diff --git a/swh/web/browse/views/content.py b/swh/web/browse/views/content.py
index fa646f90..d60652b7 100644
--- a/swh/web/browse/views/content.py
+++ b/swh/web/browse/views/content.py
@@ -1,184 +1,185 @@
 # Copyright (C) 2017-2018  The Software Heritage developers
 # See the AUTHORS file at the top-level directory of this distribution
 # License: GNU General Public License version 3, or any later version
 # See top-level LICENSE file for more information
 
 import json
 
 from django.http import HttpResponse
 from django.utils.safestring import mark_safe
 from django.shortcuts import render
 from django.template.defaultfilters import filesizeformat
 
 from swh.model.hashutil import hash_to_hex
 
 from swh.web.common import query, service
 from swh.web.common.utils import reverse, gen_path_info
 from swh.web.common.exc import handle_view_exception
 from swh.web.browse.utils import (
     request_content, prepare_content_for_display
 )
 from swh.web.browse.browseurls import browse_route
 
 
 @browse_route(r'content/(?P<query_string>.+)/raw/',
               view_name='browse-content-raw')
 def content_raw(request, query_string):
     """Django view that produces a raw display of a SWH content identified
     by its hash value.
 
     The url that points to it is :http:get:`/browse/content/[(algo_hash):](hash)/raw/`
 
     Args:
         request: input django http request
         query_string: a string of the form "[ALGO_HASH:]HASH" where
             optional ALGO_HASH can be either *sha1*, *sha1_git*, *sha256*,
             or *blake2s256* (default to *sha1*) and HASH the hexadecimal
             representation of the hash value
 
     Returns:
         The raw bytes of the content.
 
 
     """ # noqa
 
     try:
         algo, checksum = query.parse_hash(query_string)
         checksum = hash_to_hex(checksum)
         content_data = request_content(query_string)
     except Exception as exc:
         return handle_view_exception(request, exc)
 
     filename = request.GET.get('filename', None)
     if not filename:
         filename = '%s_%s' % (algo, checksum)
 
     if content_data['mimetype'].startswith('text/'):
         response = HttpResponse(content_data['raw_data'],
                                 content_type="text/plain")
         response['Content-disposition'] = 'filename=%s' % filename
     else:
         response = HttpResponse(content_data['raw_data'],
                                 content_type='application/octet-stream')
         response['Content-disposition'] = 'attachment; filename=%s' % filename
     return response
 
 
 @browse_route(r'content/(?P<query_string>.+)/metadata/',
               view_name='browse-content-metadata')
 def content_metadata(request, query_string):
     """
     Endpoint used to query content metadata asynchronously client-side.
     """
     language = service.lookup_content_language(query_string)
     license = service.lookup_content_license(query_string)
 
     content_metadata = {}
     if language:
         content_metadata['language'] = language['lang']
     else:
         content_metadata['language'] = 'not detected'
     if license:
         content_metadata['licenses'] = ', '.join(license['licenses'])
     else:
         content_metadata['licenses'] = 'not detected'
 
     content_metadata = json.dumps(content_metadata, separators=(',', ': '))
 
     return HttpResponse(content_metadata, content_type='application/json')
 
 
 @browse_route(r'content/(?P<query_string>.+)/',
               view_name='browse-content')
 def content_display(request, query_string):
     """Django view that produces an HTML display of a SWH content identified
     by its hash value.
 
     The url that points to it is :http:get:`/browse/content/[(algo_hash):](hash)/`
 
     Args:
         request: input django http request
         query_string: a string of the form "[ALGO_HASH:]HASH" where
             optional ALGO_HASH can be either *sha1*, *sha1_git*, *sha256*,
             or *blake2s256* (default to *sha1*) and HASH the hexadecimal
             representation of the hash value
 
     Returns:
         The HTML rendering of the requested content.
 
     """ # noqa
     try:
         algo, checksum = query.parse_hash(query_string)
         checksum = hash_to_hex(checksum)
         content_data = request_content(query_string)
     except Exception as exc:
         return handle_view_exception(request, exc)
 
     path = request.GET.get('path', None)
 
     content_display_data = prepare_content_for_display(
         content_data['raw_data'], content_data['mimetype'], path)
 
     root_dir = None
     filename = None
     path_info = None
 
     breadcrumbs = []
 
     if path:
         split_path = path.split('/')
         root_dir = split_path[0]
         filename = split_path[-1]
         path = path.replace(root_dir + '/', '')
         path = path[:-len(filename)]
         path_info = gen_path_info(path)
         breadcrumbs.append({'name': root_dir[:7],
                             'url': reverse('browse-directory',
                                            kwargs={'sha1_git': root_dir})})
         for pi in path_info:
             breadcrumbs.append({'name': pi['name'],
                                 'url': reverse('browse-directory',
                                                kwargs={'sha1_git': root_dir,
                                                        'path': pi['path']})})
         breadcrumbs.append({'name': filename,
                             'url': None})
 
     query_params = None
     if filename:
         query_params = {'filename': filename}
 
     content_raw_url = reverse('browse-content-raw',
                               kwargs={'query_string': query_string},
                               query_params=query_params)
 
     content_metadata = {
         'sha1 checksum': content_data['checksums']['sha1'],
         'sha1_git checksum': content_data['checksums']['sha1_git'],
         'sha256 checksum': content_data['checksums']['sha256'],
         'blake2s256 checksum': content_data['checksums']['blake2s256'],
         'mime type': content_data['mimetype'],
         'encoding': content_data['encoding'],
         'size': filesizeformat(content_data['length']),
         'language': content_data['language'],
         'licenses': content_data['licenses']
     }
 
     return render(request, 'content.html',
                   {'empty_browse': False,
                    'heading': 'Content information',
                    'top_panel_visible': True,
                    'top_panel_collapsible': True,
                    'top_panel_text': 'SWH object: Content',
                    'swh_object_metadata': content_metadata,
                    'main_panel_visible': True,
                    'content': content_display_data['content_data'],
                    'content_metadata_url': content_data['metadata_url'],
                    'mimetype': content_data['mimetype'],
                    'language': content_display_data['language'],
                    'breadcrumbs': breadcrumbs,
                    'top_right_link': content_raw_url,
                    'top_right_link_text': mark_safe(
                        '<i class="fa fa-file-text fa-fw" aria-hidden="true">'
                        '</i>Raw File'),
-                   'origin_context': None
+                   'origin_context': None,
+                   'vault_cooking': None
                    })
diff --git a/swh/web/browse/views/directory.py b/swh/web/browse/views/directory.py
index d3b9b382..f1162a76 100644
--- a/swh/web/browse/views/directory.py
+++ b/swh/web/browse/views/directory.py
@@ -1,106 +1,114 @@
 # Copyright (C) 2017-2018  The Software Heritage developers
 # See the AUTHORS file at the top-level directory of this distribution
 # License: GNU General Public License version 3, or any later version
 # See top-level LICENSE file for more information
 
 
 from django.shortcuts import render
 from django.template.defaultfilters import filesizeformat
 
 from swh.web.common import service
 from swh.web.common.utils import reverse, gen_path_info
 from swh.web.common.exc import handle_view_exception
 from swh.web.browse.utils import get_directory_entries
 
 from swh.web.browse.browseurls import browse_route
 
 
 @browse_route(r'directory/(?P<sha1_git>[0-9a-f]+)/',
               r'directory/(?P<sha1_git>[0-9a-f]+)/(?P<path>.+)/',
               view_name='browse-directory')
 def directory_browse(request, sha1_git, path=None):
     """Django view for browsing the content of a SWH directory identified
     by its sha1_git value.
 
     The url that points to it is :http:get:`/browse/directory/(sha1_git)/[(path)/]`
 
     Args:
         request: input django http request
         sha1_git: swh sha1_git identifer of the directory to browse
         path: optionnal path parameter used to navigate in directories
               reachable from the provided root one
 
     Returns:
         The HTML rendering for the content of the provided directory.
     """ # noqa
     root_sha1_git = sha1_git
     try:
         if path:
             dir_info = service.lookup_directory_with_path(sha1_git, path)
             sha1_git = dir_info['target']
 
         dirs, files = get_directory_entries(sha1_git)
     except Exception as exc:
         return handle_view_exception(request, exc)
 
     path_info = gen_path_info(path)
 
     breadcrumbs = []
     breadcrumbs.append({'name': root_sha1_git[:7],
                         'url': reverse('browse-directory',
                                        kwargs={'sha1_git': root_sha1_git})})
     for pi in path_info:
         breadcrumbs.append({'name': pi['name'],
                             'url': reverse('browse-directory',
                                            kwargs={'sha1_git': root_sha1_git,
                                                    'path': pi['path']})})
 
     path = '' if path is None else (path + '/')
 
     for d in dirs:
         d['url'] = reverse('browse-directory',
                            kwargs={'sha1_git': root_sha1_git,
                                    'path': path + d['name']})
 
     sum_file_sizes = 0
 
     readme_name = None
     readme_url = None
 
     for f in files:
         query_string = 'sha1_git:' + f['target']
         f['url'] = reverse('browse-content',
                            kwargs={'query_string': query_string},
                            query_params={'path': root_sha1_git + '/' +
                                          path + f['name']})
         sum_file_sizes += f['length']
         f['length'] = filesizeformat(f['length'])
         if f['name'].lower().startswith('readme'):
             readme_name = f['name']
             readme_sha1 = f['checksums']['sha1']
             readme_url = reverse('browse-content-raw',
                                  kwargs={'query_string': readme_sha1})
 
     sum_file_sizes = filesizeformat(sum_file_sizes)
 
     dir_metadata = {'id': sha1_git,
                     'number of regular files': len(files),
                     'number of subdirectories': len(dirs),
                     'sum of regular file sizes': sum_file_sizes}
 
+    vault_cooking = {
+        'directory_context': True,
+        'directory_id': sha1_git,
+        'revision_context': False,
+        'revision_id': None
+    }
+
     return render(request, 'directory.html',
                   {'empty_browse': False,
                    'heading': 'Directory information',
                    'top_panel_visible': True,
                    'top_panel_collapsible': True,
                    'top_panel_text': 'SWH object: Directory',
                    'swh_object_metadata': dir_metadata,
                    'main_panel_visible': True,
                    'dirs': dirs,
                    'files': files,
                    'breadcrumbs': breadcrumbs,
                    'top_right_link': None,
                    'top_right_link_text': None,
                    'readme_name': readme_name,
                    'readme_url': readme_url,
-                   'origin_context': None})
+                   'origin_context': None,
+                   'vault_cooking': vault_cooking})
diff --git a/swh/web/browse/views/origin.py b/swh/web/browse/views/origin.py
index 76008fe7..7ed233cc 100644
--- a/swh/web/browse/views/origin.py
+++ b/swh/web/browse/views/origin.py
@@ -1,849 +1,862 @@
 # Copyright (C) 2017-2018  The Software Heritage developers
 # See the AUTHORS file at the top-level directory of this distribution
 # License: GNU General Public License version 3, or any later version
 # See top-level LICENSE file for more information
 
 import dateutil
 import json
 
 from distutils.util import strtobool
 
 from django.http import HttpResponse
 from django.shortcuts import render
 from django.utils.safestring import mark_safe
 from django.template.defaultfilters import filesizeformat
 
 from swh.web.common import service
 from swh.web.common.utils import (
     gen_path_info, reverse, format_utc_iso_date
 )
 from swh.web.common.exc import NotFoundExc, handle_view_exception
 from swh.web.browse.utils import (
     get_origin_visits,
     get_directory_entries, request_content,
     prepare_content_for_display, gen_link,
     prepare_revision_log_for_display,
     get_origin_context
 )
 from swh.web.browse.browseurls import browse_route
 
 
 def _occurrence_not_found(origin_info, timestamp,
                           branch_type, occurrence, occurrences,
                           visit_id=None):
     """
     Utility function to raise an exception when a specified branch/release
     can not be found.
     """
     if branch_type:
         occ_type = 'Branch'
         occ_type_plural = 'branches'
     else:
         occ_type = 'Release'
         occ_type_plural = 'releases'
 
     if visit_id:
         if len(occurrences) == 0:
             raise NotFoundExc('Origin with type %s and url %s'
                               ' for visit with id %s has an empty list'
                               ' of %s!' % (origin_info['type'],
                                            origin_info['url'], visit_id,
                                            occ_type_plural))
         else:
             raise NotFoundExc('%s %s associated to visit with'
                               ' id %s for origin with type %s and url %s'
                               ' not found!' % (occ_type, occurrence, visit_id,
                                                origin_info['type'],
                                                origin_info['url']))
     else:
         if len(occurrences) == 0:
             raise NotFoundExc('Origin with type %s and url %s'
                               ' for visit with timestamp %s has an empty list'
                               ' of %s!' % (origin_info['type'],
                                            origin_info['url'],
                                            timestamp, occ_type_plural))
         else:
             raise NotFoundExc('%s %s associated to visit with'
                               ' timestamp %s for origin with type %s'
                               ' and url %s not found!' % (occ_type, occurrence,
                                                           timestamp,
                                                           origin_info['type'],
                                                           origin_info['url']))
 
 
 def _get_branch(branches, branch_name):
     """
     Utility function to get a specific branch from an origin branches list.
     Its purpose is to get the default HEAD branch as some SWH origin
     (e.g those with svn type) does not have it. In that latter case, check
     if there is a master branch instead and returns it.
     """
     filtered_branches = \
         [b for b in branches if b['name'].endswith(branch_name)]
     if len(filtered_branches) > 0:
         return filtered_branches[0]
     elif branch_name == 'HEAD':
         filtered_branches = \
             [b for b in branches if b['name'].endswith('master')]
         if len(filtered_branches) > 0:
             return filtered_branches[0]
         elif len(branches) > 0:
             return branches[0]
     return None
 
 
 def _get_release(releases, release_name):
     filtered_releases = \
         [r for r in releases if r['name'] == release_name]
     if len(filtered_releases) > 0:
         return filtered_releases[0]
     else:
         return None
 
 
 def _process_origin_request(request, origin_type, origin_url,
                             timestamp, path, browse_view_name):
     """
     Utility function to perform common input request processing
     for origin context views.
     """
 
     visit_id = request.GET.get('visit_id', None)
 
     origin_context = get_origin_context(origin_type, origin_url,
                                         timestamp, visit_id)
 
     for b in origin_context['branches']:
         branch_url_args = dict(origin_context['url_args'])
         if path:
             b['path'] = path
             branch_url_args['path'] = path
         b['url'] = reverse(browse_view_name,
                            kwargs=branch_url_args,
                            query_params={'branch': b['name'],
                                          'visit_id': visit_id})
 
     for r in origin_context['releases']:
         release_url_args = dict(origin_context['url_args'])
         if path:
             r['path'] = path
             release_url_args['path'] = path
         r['url'] = reverse(browse_view_name,
                            kwargs=release_url_args,
                            query_params={'release': r['name'],
                                          'visit_id': visit_id})
 
     root_sha1_git = None
     query_params = origin_context['query_params']
     revision_id = request.GET.get('revision', None)
     release_name = request.GET.get('release', None)
     branch_name = None
 
     if revision_id:
         revision = service.lookup_revision(revision_id)
         root_sha1_git = revision['directory']
         origin_context['branches'].append({'name': revision_id,
                                            'revision': revision_id,
                                            'directory': root_sha1_git,
                                            'url': None})
         branch_name = revision_id
         query_params['revision'] = revision_id
     elif release_name:
         release = _get_release(origin_context['releases'], release_name)
         if release:
             root_sha1_git = release['directory']
             query_params['release'] = release_name
             revision_id = release['target']
         else:
             _occurrence_not_found(origin_context['origin_info'], timestamp,
                                   False, release_name,
                                   origin_context['releases'], visit_id)
     else:
         branch_name = request.GET.get('branch', None)
         if branch_name:
             query_params['branch'] = branch_name
         branch = _get_branch(origin_context['branches'], branch_name or 'HEAD')
         if branch:
             branch_name = branch['name']
             root_sha1_git = branch['directory']
             revision_id = branch['revision']
 
         else:
             _occurrence_not_found(origin_context['origin_info'], timestamp,
                                   True, branch_name,
                                   origin_context['branches'], visit_id)
 
     origin_context['root_sha1_git'] = root_sha1_git
     origin_context['revision_id'] = revision_id
     origin_context['branch'] = branch_name
     origin_context['release'] = release_name
 
     return origin_context
 
 
 @browse_route(r'origin/(?P<origin_type>[a-z]+)/url/(?P<origin_url>.+)/visit/(?P<timestamp>.+)/directory/', # noqa
               r'origin/(?P<origin_type>[a-z]+)/url/(?P<origin_url>.+)/visit/(?P<timestamp>.+)/directory/(?P<path>.+)/', # noqa
               r'origin/(?P<origin_type>[a-z]+)/url/(?P<origin_url>.+)/directory/', # noqa
               r'origin/(?P<origin_type>[a-z]+)/url/(?P<origin_url>.+)/directory/(?P<path>.+)/', # noqa
               view_name='browse-origin-directory')
 def origin_directory_browse(request, origin_type, origin_url,
                             timestamp=None, path=None):
     """Django view for browsing the content of a SWH directory associated
     to an origin for a given visit.
 
     The url scheme that points to it is the following:
 
         * :http:get:`/browse/origin/(origin_type)/url/(origin_url)/directory/[(path)/]`
         * :http:get:`/browse/origin/(origin_type)/url/(origin_type)/visit/(timestamp)/directory/[(path)/]`
 
     Args:
         request: input django http request
         origin_type: the type of swh origin (git, svn, hg, ...)
         origin_url: the url of the swh origin
         timestamp: optional swh visit timestamp parameter
             (the last one will be used by default)
         path: optional path parameter used to navigate in directories
               reachable from the origin root one
         branch: optional query parameter that specifies the origin branch
             from which to retrieve the directory
         release: optional query parameter that specifies the origin release
             from which to retrieve the directory
         revision: optional query parameter to specify the origin revision
             from which to retrieve the directory
 
     Returns:
         The HTML rendering for the content of the directory associated
         to the provided origin and visit.
     """ # noqa
     try:
 
         origin_context = _process_origin_request(
             request, origin_type, origin_url, timestamp, path,
             'browse-origin-directory')
 
         root_sha1_git = origin_context['root_sha1_git']
         sha1_git = root_sha1_git
         if path:
             dir_info = service.lookup_directory_with_path(root_sha1_git, path)
             sha1_git = dir_info['target']
 
         dirs, files = get_directory_entries(sha1_git)
 
     except Exception as exc:
         return handle_view_exception(request, exc)
 
     origin_info = origin_context['origin_info']
     visit_info = origin_context['visit_info']
     url_args = origin_context['url_args']
     query_params = origin_context['query_params']
     revision_id = origin_context['revision_id']
 
     path_info = gen_path_info(path)
 
     breadcrumbs = []
     breadcrumbs.append({'name': root_sha1_git[:7],
                         'url': reverse('browse-origin-directory',
                                        kwargs=url_args,
                                        query_params=query_params)})
     for pi in path_info:
         bc_url_args = dict(url_args)
         bc_url_args['path'] = pi['path']
         breadcrumbs.append({'name': pi['name'],
                             'url': reverse('browse-origin-directory',
                                            kwargs=bc_url_args,
                                            query_params=query_params)})
 
     path = '' if path is None else (path + '/')
 
     for d in dirs:
         bc_url_args = dict(url_args)
         bc_url_args['path'] = path + d['name']
         d['url'] = reverse('browse-origin-directory',
                            kwargs=bc_url_args,
                            query_params=query_params)
 
     sum_file_sizes = 0
 
     readme_name = None
     readme_url = None
 
     for f in files:
         bc_url_args = dict(url_args)
         bc_url_args['path'] = path + f['name']
         f['url'] = reverse('browse-origin-content',
                            kwargs=bc_url_args,
                            query_params=query_params)
         sum_file_sizes += f['length']
         f['length'] = filesizeformat(f['length'])
         if f['name'].lower().startswith('readme'):
             readme_name = f['name']
             readme_sha1 = f['checksums']['sha1']
             readme_url = reverse('browse-content-raw',
                                  kwargs={'query_string': readme_sha1})
 
     history_url = reverse('browse-origin-log',
                           kwargs=url_args,
                           query_params=query_params)
 
     sum_file_sizes = filesizeformat(sum_file_sizes)
 
     browse_dir_url = reverse('browse-directory',
                              kwargs={'sha1_git': sha1_git})
 
     browse_rev_url = reverse('browse-revision',
                              kwargs={'sha1_git': revision_id},
                              query_params={'origin_type': origin_info['type'],
                                            'origin_url': origin_info['url']})
 
     dir_metadata = {'id': sha1_git,
                     'browse directory url': browse_dir_url,
                     'number of regular files': len(files),
                     'number of subdirectories': len(dirs),
                     'sum of regular file sizes': sum_file_sizes,
                     'origin id': origin_info['id'],
                     'origin type': origin_info['type'],
                     'origin url': origin_info['url'],
                     'origin visit date': format_utc_iso_date(visit_info['date']), # noqa
                     'origin visit id': visit_info['visit'],
                     'path': '/' + path,
                     'revision id': revision_id,
                     'browse revision url': browse_rev_url}
 
+    vault_cooking = {
+        'directory_context': True,
+        'directory_id': sha1_git,
+        'revision_context': True,
+        'revision_id': revision_id
+    }
+
     return render(request, 'directory.html',
                   {'empty_browse': False,
                    'heading': 'Directory information',
                    'top_panel_visible': True,
                    'top_panel_collapsible': True,
                    'top_panel_text': 'SWH object: Directory',
                    'swh_object_metadata': dir_metadata,
                    'main_panel_visible': True,
                    'dirs': dirs,
                    'files': files,
                    'breadcrumbs': breadcrumbs,
                    'top_right_link': history_url,
                    'top_right_link_text': mark_safe(
                        '<i class="fa fa-history fa-fw" aria-hidden="true"></i>'
                        'History'
                     ),
                    'readme_name': readme_name,
                    'readme_url': readme_url,
-                   'origin_context': origin_context})
+                   'origin_context': origin_context,
+                   'vault_cooking': vault_cooking})
 
 
 @browse_route(r'origin/(?P<origin_type>[a-z]+)/url/(?P<origin_url>.+)/visit/(?P<timestamp>.+)/content/(?P<path>.+)/', # noqa
               r'origin/(?P<origin_type>[a-z]+)/url/(?P<origin_url>.+)/content/(?P<path>.+)/', # noqa
               view_name='browse-origin-content')
 def origin_content_display(request, origin_type, origin_url, path,
                            timestamp=None):
     """Django view that produces an HTML display of a SWH content
     associated to an origin for a given visit.
 
     The url scheme that points to it is the following:
 
         * :http:get:`/browse/origin/(origin_type)/url/(origin_url)/content/(path)/`
         * :http:get:`/browse/origin/(origin_type)/url/(origin_url)/visit/(timestamp)/content/(path)/`
 
     Args:
         request: input django http request
         origin_type: the type of swh origin (git, svn, hg, ...)
         origin_url: the url of the swh origin
         path: path of the content relative to the origin root directory
         timestamp: optional swh visit timestamp parameter
             (the last one will be used by default)
         branch: optional query parameter that specifies the origin branch
             from which to retrieve the content
         release: optional query parameter that specifies the origin release
             from which to retrieve the content
         revision: optional query parameter to specify the origin revision
             from which to retrieve the content
 
     Returns:
         The HTML rendering of the requested content associated to
         the provided origin and visit.
 
     """ # noqa
     try:
 
         origin_context = _process_origin_request(
             request, origin_type, origin_url, timestamp, path,
             'browse-origin-content')
 
         root_sha1_git = origin_context['root_sha1_git']
         content_info = service.lookup_directory_with_path(root_sha1_git, path)
         sha1_git = content_info['target']
         query_string = 'sha1_git:' + sha1_git
         content_data = request_content(query_string)
 
     except Exception as exc:
         return handle_view_exception(request, exc)
 
     url_args = origin_context['url_args']
     query_params = origin_context['query_params']
     revision_id = origin_context['revision_id']
     origin_info = origin_context['origin_info']
     visit_info = origin_context['visit_info']
 
     content_display_data = prepare_content_for_display(
         content_data['raw_data'], content_data['mimetype'], path)
 
     filename = None
     path_info = None
 
     breadcrumbs = []
 
     split_path = path.split('/')
     filename = split_path[-1]
     path = path[:-len(filename)]
     path_info = gen_path_info(path)
     breadcrumbs.append({'name': root_sha1_git[:7],
                         'url': reverse('browse-origin-directory',
                                        kwargs=url_args,
                                        query_params=query_params)})
     for pi in path_info:
         bc_url_args = dict(url_args)
         bc_url_args['path'] = pi['path']
         breadcrumbs.append({'name': pi['name'],
                             'url': reverse('browse-origin-directory',
                                            kwargs=bc_url_args,
                                            query_params=query_params)})
 
     breadcrumbs.append({'name': filename,
                         'url': None})
 
     browse_content_url = reverse('browse-content',
                                  kwargs={'query_string': query_string})
 
     content_raw_url = reverse('browse-content-raw',
                               kwargs={'query_string': query_string},
                               query_params={'filename': filename})
 
     browse_rev_url = reverse('browse-revision',
                              kwargs={'sha1_git': revision_id},
                              query_params={'origin_type': origin_info['type'],
                                            'origin_url': origin_info['url']})
 
     content_metadata = {
         'browse content url': browse_content_url,
         'sha1 checksum': content_data['checksums']['sha1'],
         'sha1_git checksum': content_data['checksums']['sha1_git'],
         'sha256 checksum': content_data['checksums']['sha256'],
         'blake2s256 checksum': content_data['checksums']['blake2s256'],
         'mime type': content_data['mimetype'],
         'encoding': content_data['encoding'],
         'size': filesizeformat(content_data['length']),
         'language': content_data['language'],
         'licenses': content_data['licenses'],
         'origin id': origin_info['id'],
         'origin type': origin_info['type'],
         'origin url': origin_info['url'],
         'origin visit date': format_utc_iso_date(visit_info['date']),
         'origin visit id': visit_info['visit'],
         'path': '/' + path,
         'filename': filename,
         'revision id': revision_id,
         'browse revision url': browse_rev_url
     }
 
     return render(request, 'content.html',
                   {'empty_browse': False,
                    'heading': 'Content information',
                    'top_panel_visible': True,
                    'top_panel_collapsible': True,
                    'top_panel_text': 'SWH object: Content',
                    'swh_object_metadata': content_metadata,
                    'main_panel_visible': True,
                    'content': content_display_data['content_data'],
                    'content_metadata_url': content_data['metadata_url'],
                    'mimetype': content_data['mimetype'],
                    'language': content_display_data['language'],
                    'breadcrumbs': breadcrumbs,
                    'top_right_link': content_raw_url,
                    'top_right_link_text': mark_safe(
                        '<i class="fa fa-file-text fa-fw" aria-hidden="true">'
                        '</i>Raw File'),
-                   'origin_context': origin_context})
+                   'origin_context': origin_context,
+                   'vault_cooking': None
+                   })
 
 
 def _gen_directory_link(url_args, query_params, link_text):
     directory_url = reverse('browse-origin-directory',
                             kwargs=url_args,
                             query_params=query_params)
     return gen_link(directory_url, link_text)
 
 
 PER_PAGE = 20
 
 
 @browse_route(r'origin/(?P<origin_type>[a-z]+)/url/(?P<origin_url>.+)/visit/(?P<timestamp>.+)/log/', # noqa
               r'origin/(?P<origin_type>[a-z]+)/url/(?P<origin_url>.+)/log/',
               view_name='browse-origin-log')
 def origin_log_browse(request, origin_type, origin_url, timestamp=None):
     """Django view that produces an HTML display of revisions history (aka
     the commit log) associated to a SWH origin.
 
     The url scheme that points to it is the following:
 
         * :http:get:`/browse/origin/(origin_type)/url/(origin_url)/log/`
         * :http:get:`/browse/origin/(origin_type)/url/(origin_url)/visit/(timestamp)/log/`
 
     Args:
         request: input django http request
         origin_type: the type of swh origin (git, svn, hg, ...)
         origin_url: the url of the swh origin
         timestamp: optional visit timestamp parameter
             (the last one will be used by default)
         revs_breadcrumb: query parameter used internally to store
             the navigation breadcrumbs (i.e. the list of descendant revisions
             visited so far).
         per_page: optional query parameter used to specify the number of
             log entries per page
         branch: optional query parameter that specifies the origin branch
             from which to retrieve the commit log
         release: optional query parameter that specifies the origin release
             from which to retrieve the commit log
         revision: optional query parameter to specify the origin revision
             from which to retrieve the commit log
 
     Returns:
         The HTML rendering of revisions history for a given SWH visit.
 
     """ # noqa
     try:
 
         origin_context = _process_origin_request(
             request, origin_type, origin_url, timestamp, None,
             'browse-origin-log')
 
         revision_id = origin_context['revision_id']
         per_page = int(request.GET.get('per_page', PER_PAGE))
         revision_log = service.lookup_revision_log(revision_id,
                                                    limit=per_page+1)
         revision_log = list(revision_log)
 
     except Exception as exc:
         return handle_view_exception(request, exc)
 
     origin_info = origin_context['origin_info']
     visit_info = origin_context['visit_info']
     url_args = origin_context['url_args']
     query_params = origin_context['query_params']
 
     query_params['per_page'] = per_page
 
     revs_breadcrumb = request.GET.get('revs_breadcrumb', None)
 
     if revs_breadcrumb:
         revision_id = revs_breadcrumb.split('/')[-1]
 
     revision_log_display_data = prepare_revision_log_for_display(
         revision_log, per_page, revs_breadcrumb, origin_context)
 
     prev_rev = revision_log_display_data['prev_rev']
     prev_revs_breadcrumb = revision_log_display_data['prev_revs_breadcrumb']
     prev_log_url = None
     query_params['revs_breadcrumb'] = prev_revs_breadcrumb
     if prev_rev:
         prev_log_url = \
             reverse('browse-origin-log',
                     kwargs=url_args,
                     query_params=query_params)
 
     next_rev = revision_log_display_data['next_rev']
     next_revs_breadcrumb = revision_log_display_data['next_revs_breadcrumb']
     next_log_url = None
     query_params['revs_breadcrumb'] = next_revs_breadcrumb
     if next_rev:
         next_log_url = \
             reverse('browse-origin-log',
                     kwargs=url_args,
                     query_params=query_params)
 
     revision_log_data = revision_log_display_data['revision_log_data']
 
     for i, log in enumerate(revision_log_data):
         params = {
             'revision': revision_log[i]['id'],
         }
         if 'visit_id' in query_params:
             params['visit_id'] = query_params['visit_id']
         log['directory'] = _gen_directory_link(url_args, params, 'Tree')
 
     browse_log_url = reverse('browse-revision-log',
                              kwargs={'sha1_git': revision_id})
 
     revision_metadata = {
         'browse revision history url': browse_log_url,
         'origin id': origin_info['id'],
         'origin type': origin_info['type'],
         'origin url': origin_info['url'],
         'origin visit date': format_utc_iso_date(visit_info['date']),
         'origin visit id': visit_info['visit']
     }
 
     return render(request, 'revision-log.html',
                   {'empty_browse': False,
                    'heading': 'Revision history information',
                    'top_panel_visible': True,
                    'top_panel_collapsible': True,
                    'top_panel_text': 'SWH object: Revision history',
                    'swh_object_metadata': revision_metadata,
                    'main_panel_visible': True,
                    'revision_log': revision_log_data,
                    'next_log_url': next_log_url,
                    'prev_log_url': prev_log_url,
                    'breadcrumbs': None,
                    'top_right_link': None,
                    'top_right_link_text': None,
                    'include_top_navigation': True,
-                   'origin_context': origin_context})
+                   'origin_context': origin_context,
+                   'vault_cooking': None})
 
 
 @browse_route(r'origin/(?P<origin_type>[a-z]+)/url/(?P<origin_url>.+)/visit/(?P<timestamp>.+)/branches/', # noqa
               r'origin/(?P<origin_type>[a-z]+)/url/(?P<origin_url>.+)/branches/', # noqa
               view_name='browse-origin-branches')
 def origin_branches_browse(request, origin_type, origin_url, timestamp=None):
     """Django view that produces an HTML display of the list of branches
     associated to an origin for a given visit.
 
     The url scheme that points to it is the following:
 
         * :http:get:`/browse/origin/(origin_type)/url/(origin_url)/branches/`
         * :http:get:`/browse/origin/(origin_type)/url/(origin_url)/visit/(timestamp)/branches/`
 
     """ # noqa
     try:
         origin_context = _process_origin_request(
             request, origin_type, origin_url, timestamp, None,
             'browse-origin-directory')
 
     except Exception as exc:
         return handle_view_exception(request, exc)
 
     branches_offset = int(request.GET.get('branches_offset', 0))
 
     origin_info = origin_context['origin_info']
     url_args = origin_context['url_args']
     query_params = origin_context['query_params']
 
     branches = origin_context['branches']
 
     displayed_branches = \
         branches[branches_offset:branches_offset+PER_PAGE]
 
     for branch in displayed_branches:
         revision_url = reverse(
             'browse-revision', kwargs={'sha1_git': branch['revision']},
             query_params={'origin_type': origin_info['type'],
                           'origin_url': origin_info['url']})
         query_params['branch'] = branch['name']
         directory_url = reverse('browse-origin-directory',
                                 kwargs=url_args,
                                 query_params=query_params)
         del query_params['branch']
         branch['revision_url'] = revision_url
         branch['directory_url'] = directory_url
 
     prev_branches_url = None
     next_branches_url = None
 
     next_offset = branches_offset + PER_PAGE
     prev_offset = branches_offset - PER_PAGE
     if next_offset < len(branches):
         query_params['branches_offset'] = next_offset
         next_branches_url = reverse('browse-origin-branches',
                                     kwargs=url_args, query_params=query_params)
     query_params['branches_offset'] = None
     if prev_offset >= 0:
         if prev_offset != 0:
             query_params['branches_offset'] = prev_offset
         prev_branches_url = reverse('browse-origin-branches',
                                     kwargs=url_args, query_params=query_params)
 
     return render(request, 'branches.html',
                   {'empty_browse': False,
                    'heading': 'Origin branches list',
                    'top_panel_visible': False,
                    'top_panel_collapsible': False,
                    'top_panel_text': 'SWH object: Origin branches list',
                    'swh_object_metadata': {},
                    'main_panel_visible': True,
                    'top_right_link': None,
                    'top_right_link_text': None,
                    'include_top_navigation': True,
                    'displayed_branches': displayed_branches,
                    'prev_branches_url': prev_branches_url,
                    'next_branches_url': next_branches_url,
                    'origin_context': origin_context})
 
 
 @browse_route(r'origin/(?P<origin_type>[a-z]+)/url/(?P<origin_url>.+)/visit/(?P<timestamp>.+)/releases/', # noqa
               r'origin/(?P<origin_type>[a-z]+)/url/(?P<origin_url>.+)/releases/', # noqa
               view_name='browse-origin-releases')
 def origin_releases_browse(request, origin_type, origin_url, timestamp=None):
     """Django view that produces an HTML display of the list of releases
     associated to an origin for a given visit.
 
     The url scheme that points to it is the following:
 
         * :http:get:`/browse/origin/(origin_type)/url/(origin_url)/releases/`
         * :http:get:`/browse/origin/(origin_type)/url/(origin_url)/visit/(timestamp)/releases/`
 
     """ # noqa
     try:
         origin_context = _process_origin_request(
             request, origin_type, origin_url, timestamp, None,
             'browse-origin-directory')
 
     except Exception as exc:
         return handle_view_exception(request, exc)
 
     releases_offset = int(request.GET.get('releases_offset', 0))
 
     origin_info = origin_context['origin_info']
     url_args = origin_context['url_args']
     query_params = origin_context['query_params']
 
     releases = origin_context['releases']
 
     displayed_releases = \
         releases[releases_offset:releases_offset+PER_PAGE]
 
     for release in displayed_releases:
         release_url = reverse('browse-release',
                               kwargs={'sha1_git': release['id']},
                               query_params={'origin_type': origin_info['type'],
                                             'origin_url': origin_info['url']})
         query_params['release'] = release['name']
         del query_params['release']
         release['release_url'] = release_url
 
     prev_releases_url = None
     next_releases_url = None
 
     next_offset = releases_offset + PER_PAGE
     prev_offset = releases_offset - PER_PAGE
     if next_offset < len(releases):
         query_params['releases_offset'] = next_offset
         next_releases_url = reverse('browse-origin-releases',
                                     kwargs=url_args, query_params=query_params)
     query_params['releases_offset'] = None
     if prev_offset >= 0:
         if prev_offset != 0:
             query_params['releases_offset'] = prev_offset
         prev_releases_url = reverse('browse-origin-releases',
                                     kwargs=url_args, query_params=query_params)
 
     return render(request, 'releases.html',
                   {'empty_browse': False,
                    'heading': 'Origin releases list',
                    'top_panel_visible': False,
                    'top_panel_collapsible': False,
                    'top_panel_text': 'SWH object: Origin releases list',
                    'swh_object_metadata': {},
                    'main_panel_visible': True,
                    'top_right_link': None,
                    'top_right_link_text': None,
                    'include_top_navigation': True,
                    'displayed_releases': displayed_releases,
                    'prev_releases_url': prev_releases_url,
                    'next_releases_url': next_releases_url,
-                   'origin_context': origin_context})
+                   'origin_context': origin_context,
+                   'vault_cooking': None})
 
 
 @browse_route(r'origin/(?P<origin_type>[a-z]+)/url/(?P<origin_url>.+)/',
               view_name='browse-origin')
 def origin_browse(request, origin_type=None, origin_url=None):
     """Django view that produces an HTML display of a swh origin identified
     by its id or its url.
 
     The url scheme that points to it is :http:get:`/browse/origin/(origin_type)/url/(origin_url)/`.
 
     Args:
         request: input django http request
         origin_type: type of origin (git, svn, ...)
         origin_url: url of the origin (e.g. https://github.com/<user>/<repo>)
 
     Returns:
         The HMTL rendering for the metadata of the provided origin.
     """ # noqa
     try:
         origin_info = service.lookup_origin({
             'type': origin_type,
             'url': origin_url
         })
         origin_visits = get_origin_visits(origin_info)
         origin_visits.reverse()
     except Exception as exc:
         return handle_view_exception(request, exc)
 
     origin_info['last swh visit browse url'] = \
         reverse('browse-origin-directory',
                 kwargs={'origin_type': origin_type,
                         'origin_url': origin_url})
 
     origin_visits_data = []
     visits_splitted = []
     visits_by_year = {}
     for i, visit in enumerate(origin_visits):
         visit_date = dateutil.parser.parse(visit['date'])
         visit_year = str(visit_date.year)
         url_date = format_utc_iso_date(visit['date'], '%Y-%m-%dT%H:%M:%S')
         visit['fmt_date'] = format_utc_iso_date(visit['date'])
         query_params = {}
         if i < len(origin_visits) - 1:
             if visit['date'] == origin_visits[i+1]['date']:
                 query_params = {'visit_id': visit['visit']}
         if i > 0:
             if visit['date'] == origin_visits[i-1]['date']:
                 query_params = {'visit_id': visit['visit']}
 
         visit['browse_url'] = reverse('browse-origin-directory',
                                       kwargs={'origin_type': origin_type,
                                               'origin_url': origin_url,
                                               'timestamp': url_date},
                                       query_params=query_params)
         origin_visits_data.insert(0, {'date': visit_date.timestamp()})
         if visit_year not in visits_by_year:
             # display 3 years by row in visits list view
             if len(visits_by_year) == 3:
                 visits_splitted.insert(0, visits_by_year)
                 visits_by_year = {}
             visits_by_year[visit_year] = []
         visits_by_year[visit_year].append(visit)
 
     if len(visits_by_year) > 0:
         visits_splitted.insert(0, visits_by_year)
 
     return render(request, 'origin.html',
                   {'empty_browse': False,
                    'heading': 'Origin information',
                    'top_panel_visible': False,
                    'top_panel_collapsible': False,
                    'top_panel_text': 'SWH object: Visits history',
                    'swh_object_metadata': origin_info,
                    'main_panel_visible': True,
                    'origin_visits_data': origin_visits_data,
                    'visits_splitted': visits_splitted,
                    'origin_info': origin_info,
                    'browse_url_base': '/browse/origin/%s/url/%s/' %
-                   (origin_type, origin_url)})
+                   (origin_type, origin_url),
+                   'vault_cooking': None})
 
 
 @browse_route(r'origin/search/(?P<url_pattern>.+)/',
               view_name='browse-origin-search')
 def origin_search(request, url_pattern):
     """Search for origins whose urls contain a provided string pattern
     or match a provided regular expression.
     The search is performed in a case insensitive way.
     """
     offset = int(request.GET.get('offset', '0'))
     limit = int(request.GET.get('limit', '50'))
     regexp = request.GET.get('regexp', 'false')
 
     results = service.search_origin(url_pattern, offset, limit,
                                     bool(strtobool(regexp)))
 
     results = json.dumps(list(results), sort_keys=True, indent=4,
                          separators=(',', ': '))
 
     return HttpResponse(results, content_type='application/json')
diff --git a/swh/web/browse/views/person.py b/swh/web/browse/views/person.py
index 1ab88143..49959b97 100644
--- a/swh/web/browse/views/person.py
+++ b/swh/web/browse/views/person.py
@@ -1,41 +1,42 @@
 # Copyright (C) 2017-2018  The Software Heritage developers
 # See the AUTHORS file at the top-level directory of this distribution
 # License: GNU General Public License version 3, or any later version
 # See top-level LICENSE file for more information
 
 
 from django.shortcuts import render
 from swh.web.common import service
 from swh.web.common.exc import handle_view_exception
 from swh.web.browse.browseurls import browse_route
 
 
 @browse_route(r'person/(?P<person_id>[0-9]+)/',
               view_name='browse-person')
 def person_browse(request, person_id):
     """
     Django view that produces an HTML display of a swh person
     identified by its id.
 
     The url that points to it is :http:get:`/browse/person/(person_id)/`.
 
     Args:
         request: input django http request
         person_id (int): a swh person id
 
     Returns:
         The HMTL rendering for the metadata of the provided person.
     """
     try:
         person = service.lookup_person(person_id)
     except Exception as exc:
         return handle_view_exception(request, exc)
 
     return render(request, 'person.html',
                   {'empty_browse': False,
                    'heading': 'Person information',
                    'top_panel_visible': True,
                    'top_panel_collapsible': False,
                    'top_panel_text': 'SWH object: Person',
                    'swh_object_metadata': person,
-                   'main_panel_visible': False})
+                   'main_panel_visible': False,
+                   'vault_cooking': None})
diff --git a/swh/web/browse/views/revision.py b/swh/web/browse/views/revision.py
index c9462ea1..60c6db31 100644
--- a/swh/web/browse/views/revision.py
+++ b/swh/web/browse/views/revision.py
@@ -1,266 +1,274 @@
 # Copyright (C) 2017-2018  The Software Heritage developers
 # See the AUTHORS file at the top-level directory of this distribution
 # License: GNU General Public License version 3, or any later version
 # See top-level LICENSE file for more information
 
 import json
 
 from django.shortcuts import render
 from django.utils.safestring import mark_safe
 
 from swh.web.common import service
 from swh.web.common.utils import reverse, format_utc_iso_date, gen_path_info
 from swh.web.common.exc import handle_view_exception
 from swh.web.browse.browseurls import browse_route
 from swh.web.browse.utils import (
     gen_link, gen_person_link, gen_revision_link,
     prepare_revision_log_for_display,
     get_origin_context, gen_origin_directory_link,
     get_revision_log_url, get_directory_entries,
     gen_directory_link, request_content, prepare_content_for_display,
 )
 
 
 @browse_route(r'revision/(?P<sha1_git>[0-9a-f]+)/',
               view_name='browse-revision')
 def revision_browse(request, sha1_git):
     """
     Django view that produces an HTML display of a SWH revision
     identified by its id.
 
     The url that points to it is :http:get:`/browse/revision/(sha1_git)/`.
 
     Args:
         request: input django http request
         sha1_git: a SWH revision id
 
     Returns:
         The HMTL rendering for the metadata of the provided revision.
     """
     try:
         revision = service.lookup_revision(sha1_git)
         origin_info = None
         origin_context = None
         origin_type = request.GET.get('origin_type', None)
         origin_url = request.GET.get('origin_url', None)
         timestamp = request.GET.get('timestamp', None)
         visit_id = request.GET.get('visit_id', None)
         path = request.GET.get('path', None)
         dir_id = None
         dirs, files = None, None
         content_data = None
         if origin_type and origin_url:
             origin_context = get_origin_context(origin_type, origin_url,
                                                 timestamp, visit_id)
         if path:
             path_info = \
                 service.lookup_directory_with_path(revision['directory'], path)
             if path_info['type'] == 'dir':
                 dir_id = path_info['target']
             else:
                 query_string = 'sha1_git:' + path_info['target']
                 content_data = request_content(query_string)
         else:
             dir_id = revision['directory']
 
         if dir_id:
             path = '' if path is None else (path + '/')
             dirs, files = get_directory_entries(dir_id)
     except Exception as exc:
         return handle_view_exception(request, exc)
 
     revision_data = {}
 
     revision_data['author'] = gen_person_link(
         revision['author']['id'], revision['author']['name'])
     revision_data['committer'] = gen_person_link(
         revision['committer']['id'], revision['committer']['name'])
     revision_data['committer date'] = format_utc_iso_date(
         revision['committer_date'])
     revision_data['date'] = format_utc_iso_date(revision['date'])
     if origin_context:
         revision_data['directory'] = \
             gen_origin_directory_link(origin_context, sha1_git,
                                       link_text='Browse')
     else:
         revision_data['directory'] = \
             gen_directory_link(revision['directory'], link_text='Browse')
     revision_data['id'] = sha1_git
     revision_data['merge'] = revision['merge']
     revision_data['metadata'] = json.dumps(revision['metadata'],
                                            sort_keys=True,
                                            indent=4, separators=(',', ': '))
 
     if origin_info:
         browse_revision_url = reverse('browse-revision',
                                       kwargs={'sha1_git': sha1_git})
         revision_data['browse revision url'] = gen_link(browse_revision_url,
                                                         browse_revision_url)
         revision_data['origin id'] = origin_info['id']
         revision_data['origin type'] = origin_info['type']
         revision_data['origin url'] = gen_link(origin_info['url'],
                                                origin_info['url'])
 
     parents = ''
     for p in revision['parents']:
         parent_link = gen_revision_link(p, origin_context=origin_context)
         parents += parent_link + '<br/>'
 
     revision_data['parents'] = mark_safe(parents)
     revision_data['synthetic'] = revision['synthetic']
     revision_data['type'] = revision['type']
 
     message_lines = revision['message'].split('\n')
 
     parents_links = '<b>%s parent%s</b> ' %  \
         (len(revision['parents']),
          '' if len(revision['parents']) == 1 else 's')
     parents_links += '<i class="octicon octicon-git-commit fa-fw"></i> '
     for p in revision['parents']:
         parent_link = gen_revision_link(p, shorten_id=True,
                                         origin_context=origin_context)
         parents_links += parent_link
         if p != revision['parents'][-1]:
             parents_links += ' + '
 
     path_info = gen_path_info(path)
 
     query_params = {'origin_type': origin_type,
                     'origin_url': origin_url,
                     'timestamp': timestamp,
                     'visit_id': visit_id}
 
     breadcrumbs = []
     breadcrumbs.append({'name': revision['directory'][:7],
                         'url': reverse('browse-revision',
                                        kwargs={'sha1_git': sha1_git},
                                        query_params=query_params)})
     for pi in path_info:
         query_params['path'] = pi['path']
         breadcrumbs.append({'name': pi['name'],
                             'url': reverse('browse-revision',
                                            kwargs={'sha1_git': sha1_git},
                                            query_params=query_params)})
 
     content = None
     mimetype = None
     language = None
     if content_data:
         breadcrumbs[-1]['url'] = None
         content_display_data = prepare_content_for_display(
             content_data['raw_data'], content_data['mimetype'], path)
         content = content_display_data['content_data']
         mimetype = content_data['mimetype']
         language = content_display_data['language']
     else:
         for d in dirs:
             query_params['path'] = path + d['name']
             d['url'] = reverse('browse-revision',
                                kwargs={'sha1_git': sha1_git},
                                query_params=query_params)
         for f in files:
             query_params['path'] = path + f['name']
             f['url'] = reverse('browse-revision',
                                kwargs={'sha1_git': sha1_git},
                                query_params=query_params)
 
     history_url = get_revision_log_url(sha1_git, origin_context)
+    vault_cooking = {
+        'directory_context': True,
+        'directory_id': dir_id,
+        'revision_context': True,
+        'revision_id': sha1_git
+    }
 
     return render(request, 'revision.html',
                   {'empty_browse': False,
                    'heading': 'Revision information',
                    'top_panel_visible': True,
                    'top_panel_collapsible': True,
                    'top_panel_text': 'SWH object: Revision',
                    'swh_object_metadata': revision_data,
                    'message_header': message_lines[0],
                    'message_body': '\n'.join(message_lines[1:]),
                    'parents_links': mark_safe(parents_links),
                    'main_panel_visible': True,
                    'origin_context': origin_context,
                    'dirs': dirs,
                    'files': files,
                    'content': content,
                    'mimetype': mimetype,
                    'language': language,
                    'breadcrumbs': breadcrumbs,
                    'top_right_link': history_url,
                    'top_right_link_text': mark_safe(
                        '<i class="fa fa-history fa-fw" aria-hidden="true"></i>'
                        'History'
-                    )})
+                    ),
+                   'vault_cooking': vault_cooking})
 
 
 NB_LOG_ENTRIES = 20
 
 
 @browse_route(r'revision/(?P<sha1_git>[0-9a-f]+)/log/',
               view_name='browse-revision-log')
 def revision_log_browse(request, sha1_git):
     """
     Django view that produces an HTML display of the history
     log for a SWH revision identified by its id.
 
     The url that points to it is :http:get:`/browse/revision/(sha1_git)/log/`.
 
     Args:
         request: input django http request
         sha1_git: a SWH revision id
 
     Returns:
         The HMTL rendering of the revision history log.
     """ # noqa
     try:
         per_page = int(request.GET.get('per_page', NB_LOG_ENTRIES))
         revision_log = service.lookup_revision_log(sha1_git,
                                                    limit=per_page+1)
         revision_log = list(revision_log)
     except Exception as exc:
         return handle_view_exception(request, exc)
 
     revs_breadcrumb = request.GET.get('revs_breadcrumb', None)
 
     revision_log_display_data = prepare_revision_log_for_display(
         revision_log, per_page, revs_breadcrumb)
 
     prev_rev = revision_log_display_data['prev_rev']
     prev_revs_breadcrumb = revision_log_display_data['prev_revs_breadcrumb']
     prev_log_url = None
     if prev_rev:
         prev_log_url = \
             reverse('browse-revision-log',
                     kwargs={'sha1_git': prev_rev},
                     query_params={'revs_breadcrumb': prev_revs_breadcrumb,
                                   'per_page': per_page})
 
     next_rev = revision_log_display_data['next_rev']
     next_revs_breadcrumb = revision_log_display_data['next_revs_breadcrumb']
     next_log_url = None
     if next_rev:
         next_log_url = \
             reverse('browse-revision-log',
                     kwargs={'sha1_git': next_rev},
                     query_params={'revs_breadcrumb': next_revs_breadcrumb,
                                   'per_page': per_page})
 
     revision_log_data = revision_log_display_data['revision_log_data']
 
     for log in revision_log_data:
         log['directory'] = gen_directory_link(log['directory'], 'Tree')
 
     return render(request, 'revision-log.html',
                   {'empty_browse': False,
                    'heading': 'Revision history information',
                    'top_panel_visible': False,
                    'top_panel_collapsible': False,
                    'top_panel_text': 'SWH object: Revision history',
                    'swh_object_metadata': None,
                    'main_panel_visible': True,
                    'revision_log': revision_log_data,
                    'next_log_url': next_log_url,
                    'prev_log_url': prev_log_url,
                    'breadcrumbs': None,
                    'top_right_link': None,
                    'top_right_link_text': None,
                    'include_top_navigation': False,
-                   'origin_context': None})
+                   'origin_context': None,
+                   'vault_cooking': None})
diff --git a/swh/web/static/css/style.css b/swh/web/static/css/style.css
index 1e5746ab..40d7c31c 100644
--- a/swh/web/static/css/style.css
+++ b/swh/web/static/css/style.css
@@ -1,611 +1,644 @@
 /*
 version:   0.1
 date:      21/09/15
 author:    swh
 email:     swh
 website:   softwareheritage.org
 version history: /style.css
 */
 
 @import url(https://fonts.googleapis.com/css?family=Alegreya:400,400italic,700,700italic);
 @import url(https://fonts.googleapis.com/css?family=Alegreya+Sans:400,400italic,500,500italic,700,700italic,100,300,100italic,300italic);
 
 html {
     height: 100%;
     overflow-x: hidden;
 }
 
 body {
     font-family: 'Alegreya Sans', sans-serif;
     font-size: 1.7rem;
     line-height: 1.5;
     color: rgba(0, 0, 0, 0.55);
     padding-bottom: 120px;
     min-height: 100%;
     margin: 0;
     position: relative;
 }
 
 .heading {
     font-family: 'Alegreya', serif;
 }
 
 .shell, .text {
     font-size: 0.7em;
 }
 
 .logo img {
     max-height: 40px;
 }
 .logo .navbar-brand {
     padding: 5px;
 }
 .logo .sitename {
     padding: 15px 5px;
 }
 
 .jumbotron {
     padding: 0;
     background-color: rgba(0, 0, 0, 0);
     position: fixed;
     top: 0;
     width: 100%;
     z-index: 10;
 }
 
 #swh-navbar-collapse {
     border-top-style: none;
     border-left-style: none;
     border-right-style: none;
     border-bottom: 5px solid;
     border-image: linear-gradient(to right, rgb(226, 0, 38) 0%, rgb(254, 205, 27) 100%) 1 1 1 1;
     width: 100%;
     padding: 5px;
 }
 
 .nav-horizontal {
     float: right;
 }
 
 h3[id], h4[id], a[id] {  /* avoid in-page links covered by navbar */
     padding-top: 80px;
     margin-top: -70px;
 }
 
 h1, h2, h3, h4  {
     margin: 0;
     color: #e20026;
     padding-bottom: 10px;
 }
 h1 { font-size: 1.8em; }
 h2 { font-size: 1.2em; }
 h3 { font-size: 1.1em; }
 
 a {
     color: rgba(0, 0, 0, 0.75);
     border-bottom-style: dotted;
     border-bottom-width: 1px;
     border-bottom-color: rgb(91, 94, 111);
 }
 
 a:hover {
     color: black;
 }
 
 ul.dropdown-menu a,
 .navbar-header a,
 ul.navbar-nav a { /* No decoration on links in dropdown menu */
     border-bottom-style: none;
     color: #323232;
     font-weight: 700;
 }
 
 .navbar-header a:hover,
 ul.navbar-nav a:hover {
     color: #8f8f8f;
 }
 
 .sitename .first-word, .sitename .second-word {
     color: rgba(0, 0, 0, 0.75);
     font-weight: normal;
     font-size: 1.8rem;
 }
 
 .sitename .first-word {
     font-family: 'Alegreya Sans', sans-serif;
 }
 
 .sitename .second-word {
     font-family: 'Alegreya', serif;
 }
 
 ul.dropdown-menu > li,
 ul.dropdown-menu > li > ul > li { /* No decoration on bullet points in dropdown menu */
     list-style-type: none;
 }
 
 .page {
     margin: 2em auto;
     width: 35em;
     border: 5px solid #ccc;
     padding: 0.8em;
     background: white;
 }
 .entries {
     list-style: none;
     margin: 0;
     padding: 0;
 }
 .entries li {
     margin: 0.8em 1.2em;
 }
 .entries li h2 {
     margin-left: -1em;
 }
 .add-entry {
     font-size: 0.9em;
     border-bottom: 1px solid #ccc;
 }
 .add-entry dl {
     font-weight: bold;
 }
 .metanav {
     text-align: right;
     font-size: 0.8em;
     padding: 0.3em;
     margin-bottom: 1em;
     background: #fafafa;
 }
 .flash {
     background: #cee5F5;
     padding: 0.5em;
     border: 1px solid #aacbe2;
 }
 .error {
     background: #f0d6d6;
     padding: 0.5em;
 }
 
 .file-found {
     color: #23BA49;
 }
 .file-notfound {
     color: #FF4747;
 }
 
 /* Bootstrap custom styling to correctly render multiple
  * form-controls in an input-group:
  *                github.com/twbs/bootstrap/issues/12732 */
 
 .input-group-field {
     display: table-cell;
     vertical-align: middle;
     border-radius:4px;
     min-width:1%;
     white-space: nowrap;
 }
 
 .input-group-field .form-control {
     border-radius: inherit !important;
 }
 
 .input-group-field:not(:first-child):not(:last-child) {
     border-radius:0;
 }
 
 .input-group-field:not(:first-child):not(:last-child) .form-control {
     border-left-width: 0;
     border-right-width: 0;
 }
 
 .input-group-field:last-child {
     border-top-left-radius:0;
     border-bottom-left-radius:0;
 }
 
 .input-group > span:not(:last-child) > button {
    border-radius: 0;
 }
 
 .multi-input-group > .input-group-btn {
   vertical-align: bottom;
   padding: 0;
 }
 
 .dataTables_filter {
     margin-top: 15px;
 }
 
 .dataTables_filter input {
     width: 70%;
     float: right;
 }
 
 tr.api-doc-route-upcoming > td, tr.api-doc-route-upcoming > td > a {
     font-size: 90%;
 }
 
 tr.api-doc-route-deprecated > td, tr.api-doc-route-deprecated > td > a {
     color: red;
 }
 
 #back-to-top {
   display: initial;
   position: fixed;
   bottom: 30px;
   right: 30px;
   z-index: 10;
 }
 
 #back-to-top a img {
   display: block;
   width: 32px;
   height: 32px;
   background-size: 32px 32px;
   text-indent: -999px;
   overflow: hidden;
 }
 
 .table > thead > tr > th {
     border-bottom: 1px solid #e20026;
 }
 
 .table > tbody > tr > td {
     border-style: none;
 }
 
 pre {
     background-color: #f5f5f5;
 }
 
 .dataTables_wrapper {
     position: static;
 }
 
 /* breadcrumbs */
 
 .bread-crumbs{
     display: inline-block;
     overflow: hidden;
     color: rgba(0, 0, 0, 0.55);
 }
 
 bread-crumbs ul {
     list-style-type: none;
 }
 
 .bread-crumbs li {
     float: left;
     list-style-type: none;
 }
 
 .bread-crumbs a {
     color: rgba(0, 0, 0, 0.75);
     border-bottom-style: none;
 }
 
 .bread-crumbs a:hover {
     color: rgba(0, 0, 0, 0.85);
     text-decoration: underline;
 }
 
 .title-small .bread-crumbs{
     margin: -30px 0 25px;
 }
 
 #footer {
     background-color: #262626;
     color: hsl(0, 0%, 100%);
     font-size: 1.2rem;
     text-align: center;
     padding-top: 20px;
     padding-bottom: 20px;
     position: absolute;
     bottom: 0;
     left: 0;
     right: 0;
 }
 #footer a,
 #footer a:visited {
     color: hsl(0, 0%, 100%);
 }
 #footer a:hover {
     text-decoration: underline;
 }
 
 .highlightjs pre {
     background-color: transparent;
     border-radius: 0px;
     border-color: transparent;
 }
 
 .hljs {
     background-color: transparent;
     white-space: pre;
 }
 
 .scrollable-menu {
     max-height: 180px;
     overflow-x: hidden;
 }
 
 .swh-browse-top-navigation {
     border-bottom: 1px solid #ddd;
     min-height: 42px;
     padding: 4px 5px 0px 5px;
 }
 
 .swh-browse-bread-crumbs {
     font-size: inherit;
     vertical-align: text-top;
     margin-bottom: 1px;
 }
 
 .swh-browse-bread-crumbs li:nth-child(n+2)::before {
     content: "";
     display: inline-block;
     margin: 0 2px;
 }
 
 .swh-metadata-table-row {
     border-top: 1px solid #ddd !important;
 }
 
 /* for block of numbers */
 td.hljs-ln-numbers {
 	-webkit-touch-callout: none;
 	-webkit-user-select: none;
 	-khtml-user-select: none;
 	-moz-user-select: none;
 	-ms-user-select: none;
 	user-select: none;
 
 	text-align: center;
 	color: #ccc;
 	border-right: 1px solid #CCC;
 	vertical-align: top;
 	padding-right: 5px;
 
 	/* your custom style here */
 }
 
 /* for block of code */
 td.hljs-ln-code {
 	padding-left: 10px;
 }
 
 .btn-swh {
     color: #6C6C6C;
     background-color: #EAEAEA;
     border-color: #ddd;
     background-image: linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);
     background-repeat: repeat-x;
     outline: none;
 }
 
 .btn-swh:hover,
 .btn-swh:focus,
 .btn-swh:active,
 .btn-swh.active,
 .open .dropdown-toggle.btn-swh {
     background-color: #e6ebf1;
     background-image: linear-gradient(to bottom,#f1f1f1 0,#e6e6e6 100%);
     border-color: rgb(197, 197, 197);
 }
 
 .btn-swh.disabled,
 .btn-swh[disabled],
 fieldset[disabled] .btn-swh,
 .btn-swh.disabled:hover,
 .btn-swh[disabled]:hover,
 fieldset[disabled] .btn-swh:hover,
 .btn-swh.disabled:focus,
 .btn-swh[disabled]:focus,
 fieldset[disabled] .btn-swh:focus,
 .btn-swh.disabled:active,
 .btn-swh[disabled]:active,
 fieldset[disabled] .btn-swh:active,
 .btn-swh.disabled.active,
 .btn-swh[disabled].active,
 fieldset[disabled] .btn-swh.active {
     background-color: #EAEAEA;
     border-color: #EAEAEA;
 }
 
 .btn-swh .badge {
     color: #EAEAEA;
     background-color: #6C6C6C;
 }
 
 .btn-swh a {
     color: #6C6C6C;
     border: none;
     outline: none;
     text-decoration: none;
 }
 
 .swh-http-error {
     margin: 0 auto;
     text-align: center;
 }
 
 .swh-http-error-head {
     color: #2d353c;
     font-size: 30px;
 }
 
 .swh-http-error-code {
     bottom: 60%;
     color: #2d353c;
     font-size: 96px;
     line-height: 80px;
     margin-bottom: 10px!important;
 }
 
 .swh-http-error-desc {
     font-size: 12px;
     color: #647788;
     text-align: center;
 }
 
 .swh-http-error-desc pre {
     display: inline-block;
     text-align: left;
     max-width: 800px;
     white-space: pre-wrap;
 }
 
 .swh-table {
     border-bottom: none !important;
+    margin-bottom: 0px !important;
+}
+
+.swh-table td {
+    vertical-align: middle !important;
 }
 
 .swh-counter {
     font-size: 150%;
 }
 .swh-loading {
     display : none;
 }
 
 .swh-loading.show {
     display:inline-block;
     position: fixed;
     background: white;
     border: 1px solid black;
     top: 50%;
     left: 50%;
     margin: -50px 0px 0px -50px;
     text-align: center;
     z-index:100;
 }
 
 .swh-readme a {
     outline: none;
     border: none;
 }
 
 .swh-readme table {
     border-collapse: collapse;
 }
 
 .swh-readme table,
 .swh-readme table th,
 .swh-readme table td {
     padding: 6px 13px;
     border: 1px solid #dfe2e5;
 }
 
 .swh-readme table tr:nth-child(even) {
     background-color: #f2f2f2;
 }
 
 .swh-web-app-link:hover {
     background-color: #efeff2;
 }
 
 .swh-web-app-link a {
     text-decoration: none;
     outline: none;
     border: none;
 }
 
+.popover {
+    max-width: 100%;
+}
+
+.btn-swh-vault {
+    border-top-right-radius: 4px !important;
+    border-bottom-right-radius: 4px !important;
+}
+
 .pager a {
     outline: none;
 }
 
 .swh-content {
     background-image: none;
     border: none;
     background-color: white;
 }
 
 .swh-visit-full {
     color: green;
     position: relative;
 }
 
 .swh-visit-full:before {
     content: "\f00c";
     font-family: FontAwesome;
     left:-20px;
     position:absolute;
     top:-2px;
  }
 
 .swh-visit-partial {
     color: #edc344;
     position: relative;
 }
 
 .swh-visit-partial:before {
     content: "\f071";
     font-family: FontAwesome;
     left:-20px;
     position:absolute;
     top:-2px;
  }
 
  .swh-branches-releases {
      min-width: 200px;
  }
 
  .swh-branches-switch,
  .swh-releases-switch {
      padding: 5px 15px !important;
  }
 
  li.swh-branch:hover,
  li.swh-release:hover {
      background-color: #e8e8e8;
  }
 
  .swh-branch a,
  .swh-release a {
      outline:none;
  }
 
  .swh-branch a:hover,
  .swh-release a:hover {
     text-decoration: none;
 }
 
 .swh-origin-visit-details {
     text-align: center;
 }
 
 .swh-origin-visit-details ul {
     list-style: none;
     margin: 0;
     padding: 0;
 }
 
 .swh-origin-visit-details li {
     display: inline-block;
     vertical-align:  middle;
     margin-left: 10px;
     margin-right: 10px;
 }
 
 .swh-browse-nav li a {
     border-radius: 4px;
 }
 
 .swh-corner-ribbon {
     width: 200px;
     background: #e43;
     position: absolute;
     top: 25px;
     left: -50px;
     text-align: center;
     line-height: 50px;
     letter-spacing: 1px;
     color: #f0f0f0;
     transform: rotate(-45deg);
     -webkit-transform: rotate(-45deg);
     box-shadow: 0 0 3px rgba(0,0,0,.3);
     top: 25px;
     right: -50px;
     left: auto;
     transform: rotate(45deg);
     -webkit-transform: rotate(45deg);
     z-index: 2000;
-  }
\ No newline at end of file
+  }
+
+  .modal {
+    text-align: center;
+    padding: 0!important;
+  }
+
+  .modal:before {
+    content: '';
+    display: inline-block;
+    height: 100%;
+    vertical-align: middle;
+    margin-right: -4px;
+  }
+
+  .modal-dialog {
+    display: inline-block;
+    text-align: left;
+    vertical-align: middle;
+  }
diff --git a/swh/web/templates/browse.html b/swh/web/templates/browse.html
index 418db661..16ab8f39 100644
--- a/swh/web/templates/browse.html
+++ b/swh/web/templates/browse.html
@@ -1,156 +1,192 @@
 {% extends "layout.html" %}
 {% load swh_templatetags %}
 
 {% block title %}{{ heading }} &ndash; Software Heritage archive {% endblock %}
 
 {% block navbar-content %}
   <ul class="nav navbar-nav swh-browse-nav">
     <li class="active">
       <a href="#search" data-toggle="tab" style="outline:none;">Search</a>
     </li>
     <li>
       <a  href="#help" data-toggle="tab" style="outline:none;">Help</a>
     </li>
+    <li>
+      <a  href="#vault" data-toggle="tab" style="outline:none;">Vault</a>
+    </li>
     <li>
       <a  href="#browse" data-toggle="tab" style="outline:none;">Browse</a>
     </li>
   </ul>
 {% endblock %}
 
 {% block content %}
 
   <div class="swh-corner-ribbon">Alpha version</div>
 
   <div class="tab-content" style="margin-top: 5px;">
 
     <div class="tab-pane active" id="search">
       {% include "includes/origins-search.html" %}
     </div>
 
     <div class="tab-pane" id="help">
       {% include "includes/browse-help.html" %}
     </div>
 
+    <div class="tab-pane" id="vault">
+      {% include "includes/vault-ui.html" %}
+    </div>
+
     <div class="tab-pane" id="browse">
 
       {% if empty_browse %}
 
         <div class="panel panel-default" style="overflow-x: auto;">
           <div class="panel-heading">
             <h2>Browse the Software Heritage archive</h2>
           </div>
           <div class ="panel-body">
             <p>
               No Software Heritage object currently browsed.
               <br/>
               To browse the content of the archive, you can either use
               the <a href="{% url 'browse-homepage' %}#search">Search</a> interface or refer to the URI scheme described
               in the <a href="{% url 'browse-homepage' %}#help">Help</a> page.
             </p>
           </div>
         </div>
 
       {% else %}
 
         <div class="panel-group" id="accordion">
 
           {% block swh-browse-before-panels %}{% endblock %}
 
           {% if top_panel_visible %}
             <div class="panel panel-default" style="overflow-x: auto;">
               <div class="panel-heading">
                 {% if top_panel_collapsible %}
                   <a data-toggle="collapse" data-parent="#accordion" href="#swh-browse-top-collapse" style="outline:none;">
                 {% endif %}
                 <div class="pull-left">
                   <h2>{{ top_panel_text }}</h2>
                 </div>
                 <div class="clearfix"></div>
                 {% if top_panel_collapsible %}
                   </a>
                 {% endif %}
               </div>
               {% if top_panel_collapsible %}
                 <div id="swh-browse-top-collapse" class="panel-collapse collapse">
               {% endif %}
               <table class="table">
                 <tbody>
                   {% for key, val in swh_object_metadata.items|dictsort:"0.lower" %}
                     <tr>
                       <th class="swh-metadata-table-row">{{ key }}</th>
                       <td class="swh-metadata-table-row">
                         <pre>{{ val | safe | urlize_links_and_mails | safe }}</pre>
                       </td>
                     </tr>
                   {% endfor %}
                 </tbody>
               </table>
               {% if top_panel_collapsible %}
                 </div>
               {% endif %}
             </div>
           {% endif %}
 
           {% if main_panel_visible %}
             <div class="panel panel-default" style="overflow-x: auto;">
               {% block swh-browse-main-panel-content %}{% endblock %}
             </div>
           {% endif %}
 
           {% block swh-browse-panels-group-end %}{% endblock %}
 
         </div>
 
         {% block swh-browse-after-panels %}{% endblock %}
 
       </div>
     {% endif %}
 
   </div>
 
   <script>
 
+    var browse_tabs_hash = ["#browse", "#search", "#help", "#vault"];
+
     function removeHash () {
       history.replaceState("", document.title, window.location.pathname + window.location.search);
     }
 
-    var browse_tabs_hash = ["#browse", "#search", "#help"];
+    function show_tab(hash) {
+      $('.navbar-nav.swh-browse-nav a[href="' + hash + '"]').tab('show');
+    }
 
-    // Javascript to enable link to tab
     function show_requested_tab() {
       var hash = window.location.hash;
       if (hash && browse_tabs_hash.indexOf(hash) == -1) {
         return;
       }
       if (hash) {
-        $('.navbar-nav.swh-browse-nav a[href="' + hash + '"]').tab('show');
+        show_tab(hash);
       } else {
-        $('.navbar-nav.swh-browse-nav a[href="#browse"]').tab('show');
+        show_tab('#browse');
       }
-        window.scrollTo(0, 0);
     }
 
+    $('[data-toggle=popover]:not([data-popover-content])').popover();
+    $('[data-toggle=popover][data-popover-content]').popover({
+        html : true,
+        container: 'body',
+        trigger: 'focus',
+        content: function() {
+          var content = $(this).attr("data-popover-content");
+          return $(content).children(".popover-body").html();
+        },
+        title: function() {
+          var title = $(this).attr("data-popover-content");
+          return $(title).children(".popover-heading").html();
+        }
+    }).click(function(e) {
+      e.preventDefault();
+    });;
+
+    // Change hash for page reload
+    $('.nav-tabs a').on('shown.bs.tab', function (e) {
+      if (e.target.hash != '#browse') {
+        window.location.hash = e.target.hash;
+      } else {
+        $('.navbar-nav.swh-browse-nav a[href="#browse"]').tab('show');
+      }
+      window.scrollTo(0, 0);
+    });
+
     // show requested tab when loading the page
     $(document).ready(function() {
 
       // Change hash for page reload
       $('.navbar-nav.swh-browse-nav a').on('shown.bs.tab', function (e) {
         if (e.target.hash != '#browse') {
           window.location.hash = e.target.hash;
         } else {
           removeHash();
         }
         show_requested_tab();
       });
 
       // update displayed tab when the url fragment changes
       $(window).on('hashchange', function() {
         show_requested_tab();
       });
 
       show_requested_tab();
     });
 
   </script>
 
 {% endblock %}
diff --git a/swh/web/templates/includes/top-navigation.html b/swh/web/templates/includes/top-navigation.html
index 629e65a4..0e6377c8 100644
--- a/swh/web/templates/includes/top-navigation.html
+++ b/swh/web/templates/includes/top-navigation.html
@@ -1,119 +1,123 @@
+{% load swh_templatetags %}
 
 <div class="swh-browse-top-navigation">
   {% if origin_context and origin_context.branch %}
     <div class="dropdown" style="float: left;" id="swh-branches-releases-dd">
       <button class="btn btn-md btn-swh dropdown-toggle" type="button" data-toggle="dropdown">
         {% if origin_context.branch %}
           <i class="fa fa-code-fork fa-fw" aria-hidden="true"></i>
           Branch: <strong>{{ origin_context.branch }}</strong>
         {% else %}
           <i class="fa fa-tag fa-fw" aria-hidden="true"></i>
           Release: <strong>{{ origin_context.release }}</strong>
         {% endif %}
         <span class="caret"></span>
       </button>
       <ul class="scrollable-menu dropdown-menu swh-branches-releases">
         <div class="tabbable tabs-top">
           <ul class="nav nav-tabs">
             <li class="active"><a class="swh-branches-switch" data-toggle="tab">Branches</a></li>
             <li><a class="swh-releases-switch" data-toggle="tab">Releases</a></li>
           </ul>
           <div class="tab-content">
             <div class="tab-pane active" id="swh-tab-branches">
               {% for b in origin_context.branches %}
                 <li class="swh-branch">
                   <a href="{{ b.url | safe }}">
                     <i class="fa fa-code-fork fa-fw" aria-hidden="true"></i>
                     {% if b.name == origin_context.branch %}
                       <i class="fa fa-check fa-fw" aria-hidden="true"></i>
                     {% else %}
                       <i class="fa fa-fw" aria-hidden="true"></i>
                     {% endif %}
                     {{ b.name }}
                   </a>
                 </li>
               {% endfor %}
             </div>
             <div class="tab-pane" id="swh-tab-releases">
               {% if origin_context.releases %}
                 {% for r in origin_context.releases %}
                   {% if r.target_type == 'revision' %}
                     <li class="swh-release">
                       <a href="{{ r.url | safe }}">
                         <i class="fa fa-tag fa-fw" aria-hidden="true"></i>
                         {% if r.name == origin_context.release %}
                           <i class="fa fa-check fa-fw" aria-hidden="true"></i>
                         {% else %}
                           <i class="fa fa-fw" aria-hidden="true"></i>
                         {% endif %}
                         {{ r.name }}
                       </a>
                     </li>
                   {% endif %}
                 {% endfor %}
               {% else %}
                 <span>No releases to show</span>
               {% endif %}
             </div>
           </div>
         </div>
       </ul>
     </div>
   {% endif %}
 
-  {% if top_right_link %}
-    <a href="{{ top_right_link | safe }}" class="btn btn-md btn-swh pull-right" role="button">{{ top_right_link_text }}</a>
-  {% endif %}
+  <div class="btn-group pull-right">
+    {% if top_right_link %}
+      <a href="{{ top_right_link | safe }}" class="btn btn-md btn-swh" role="button">{{ top_right_link_text }}</a>
+    {% endif %}
+    {% include "includes/vault-create-tasks.html" %}
+  </div>
 
   {% include "includes/breadcrumbs.html" %}
 
 </div>
 
 <script>
 
   function setBranchesTabActive() {
     $('.swh-releases-switch').parent().removeClass('active');
     $('.swh-branches-switch').parent().addClass('active');
     $('#swh-tab-releases').removeClass('active');
     $('#swh-tab-branches').addClass('active');
   }
 
   function setReleasesTabActive() {
     $('.swh-branches-switch').parent().removeClass('active');
     $('.swh-releases-switch').parent().addClass('active');
     $('#swh-tab-branches').removeClass('active');
     $('#swh-tab-releases').addClass('active');
   }
 
   $('.dropdown-menu a.swh-branches-switch').click(function(e) {
     setBranchesTabActive();
     e.stopPropagation();
   });
 
   $('.dropdown-menu a.swh-releases-switch').click(function(e) {
     setReleasesTabActive();
     e.stopPropagation();
   });
 
   var dd_resized = false;
 
   // hack to resize the branches/releases dropdown content,
   // taking icons into account, in order to make the whole names readable
   $('#swh-branches-releases-dd').on('show.bs.dropdown', function () {
     if (dd_resized) return;
     var dd_width = $('.swh-branches-releases').width();
     $('.swh-branches-releases').width(dd_width + 25);
     dd_resized = true;
   })
 
   $(document).ready(function() {
     {% if origin_context %}
       if ('{{ origin_context.branch }}' != 'None') {
         setBranchesTabActive();
       } else {
         setReleasesTabActive();
       }
     {% endif %}
   });
 
 </script>
diff --git a/swh/web/templates/includes/vault-create-tasks.html b/swh/web/templates/includes/vault-create-tasks.html
new file mode 100644
index 00000000..c04703cb
--- /dev/null
+++ b/swh/web/templates/includes/vault-create-tasks.html
@@ -0,0 +1,157 @@
+{% if vault_cooking %}
+  <a class="btn btn-md btn-swh btn-swh-vault" data-placement="bottom" data-popover-content="#vault-popover" data-toggle="popover" href="#" tabindex="0">
+    <i class="fa fa-download fa-fw" aria-hidden="true"></i>Download
+  </a>
+  <div class="hidden" id="vault-popover">
+    <div class="popover-heading">
+      Request download from the Software Heritage Vault
+    </div>
+    <div class="popover-body">
+      <div class="btn-group-vertical">
+        {% if vault_cooking.directory_context %}
+          <button id="vault-cook-directory" type="button" class="btn btn-md btn-swh" data-toggle="modal" data-target="#vault-cook-directory-modal">Cook a standard archive from the current directory</button>
+        {% endif %}
+        {% if vault_cooking.revision_context %}
+          <button id="vault-cook-revision" type="button" class="btn btn-md btn-swh" data-toggle="modal" data-target="#vault-cook-revision-modal">Cook a git fast-import archive from the current revision</button>
+        {% endif %}
+      </div>
+    </div>
+  </div>
+  <div class="modal fade" id="vault-cook-directory-modal" tabindex="-1" role="dialog" aria-labelledby="vault-cook-directory-modal-label" aria-hidden="true">
+    <div class="modal-dialog">
+      <div class="modal-content">
+        <div class="modal-header">
+          <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+          <h4 class="modal-title" id="vault-cook-directory-modal-label">Cook and download a directory from the Software Heritage Vault</h4>
+        </div>
+        <div class="modal-body">
+          <p>
+            You have requested the cooking of the directory with identifier <strong>{{ vault_cooking.directory_id }}</strong>
+            into a standard tar.gz archive.
+          </p>
+          <p>
+            Are you sure you want to continue ?
+          </p>
+          <form>
+            <div class="form-group">
+              <label for="email">(Optional) Send download link once it is available to that email address:</label>
+              <input type="email" class="form-control" id="swh-vault-directory-email">
+            </div>
+          </form>
+        </div>
+        <div class="modal-footer">
+          <button type="button" class="btn btn-md btn-swh" data-dismiss="modal">Cancel</button>
+          <button type="button" class="btn btn-md btn-swh" onclick="vault_cook_directory_archive()">Ok</button>
+        </div>
+      </div>
+    </div>
+  </div>
+  <div class="modal fade" id="vault-cook-revision-modal" tabindex="-1" role="dialog" aria-labelledby="vault-cook-revision-modal-label" aria-hidden="true">
+    <div class="modal-dialog">
+      <div class="modal-content">
+        <div class="modal-header">
+          <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+          <h4 class="modal-title" id="vault-cook-revision-modal-label">Cook and download a revision from the Software Heritage Vault</h4>
+        </div>
+        <div class="modal-body">
+          <p>
+            You have requested the cooking of the revision with identifier <strong>{{ vault_cooking.revision_id }}</strong>
+            into a git fast-import archive.
+          </p>
+          <p>
+            Are you sure you want to continue ?
+          </p>
+          <form>
+            <div class="form-group">
+              <label for="email">(Optional) Send download link once it is available to that email address:</label>
+              <input type="email" class="form-control" id="swh-vault-revision-email">
+            </div>
+          </form>
+        </div>
+        <div class="modal-footer">
+          <button type="button" class="btn btn-md btn-swh" data-dismiss="modal">Cancel</button>
+          <button type="button" class="btn btn-md btn-swh" onclick="vault_cook_revision_archive()">Ok</button>
+        </div>
+      </div>
+    </div>
+  </div>
+  <div class="modal fade" id="invalid-email-modal" tabindex="-1" role="dialog" aria-labelledby="invalid-email-modal-label" aria-hidden="true">
+    <div class="modal-dialog">
+      <div class="modal-content">
+        <div class="modal-header">
+          <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+          <h4 class="modal-title" id="invalid-email-modal-label">Invalid Email !</h4>
+        </div>
+        <div class="modal-body">
+          <p>The provided email is not well-formed.</p>
+        </div>
+        <div class="modal-footer">
+          <button type="button" class="btn btn-md btn-swh" data-dismiss="modal">Ok</button>
+        </div>
+      </div>
+    </div>
+  </div>
+  <script>
+    var cook_dir_url = "{% url 'vault-cook-directory' 'ffff' %}";
+    var cook_rev_url = "{% url 'vault-cook-revision_gitfast' 'ffff' %}";
+    function add_vault_cooking_task(cooking_task) {
+      var vault_cooking_tasks = JSON.parse(sessionStorage.getItem("swh-vault-cooking-tasks"));
+      if (!vault_cooking_tasks) {
+        vault_cooking_tasks = [];
+      }
+      if (vault_cooking_tasks.find(function(val) {
+          return val.object_type == cooking_task.object_type &&
+                  val.object_id == cooking_task.object_id}) == undefined) {
+        var cooking_url;
+        if (cooking_task.object_type == 'directory') {
+          cooking_url = cook_dir_url.replace('ffff', cooking_task.object_id);
+        } else {
+          cooking_url = cook_rev_url.replace('ffff', cooking_task.object_id);
+        }
+        if (cooking_task.email) {
+          cooking_url += "?email=" + cooking_task.email;
+        }
+        $.ajax({
+          url : cooking_url,
+          type : 'POST',
+          success: function() {
+            vault_cooking_tasks.push(cooking_task);
+            sessionStorage.setItem("swh-vault-cooking-tasks", JSON.stringify(vault_cooking_tasks));
+            show_tab('#vault');
+          }
+        });
+      }
+    }
+    function validateEmail(email) {
+      var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+      return re.test(String(email).toLowerCase());
+    }
+    function vault_cook_directory_archive() {
+      var email = $('#swh-vault-directory-email').val().trim();
+      if (!email || validateEmail(email)) {
+        $('#vault-cook-directory-modal').modal('hide');
+        var cooking_task = {'object_type': 'directory',
+                            'object_id': '{{ vault_cooking.directory_id }}',
+                            'email': email,
+                            'status': 'new'};
+        add_vault_cooking_task(cooking_task);
+
+      } else {
+        $('#invalid-email-modal').modal('show');
+      }
+    }
+    function vault_cook_revision_archive() {
+      var email = $('#swh-vault-revision-email').val().trim();
+      if (!email || validateEmail(email)) {
+        $('#vault-cook-revision-modal').modal('hide');
+        var cooking_task = {'object_type': 'revision',
+                            'object_id': '{{ vault_cooking.revision_id }}',
+                            'email': email,
+                            'status': 'new'};
+        add_vault_cooking_task(cooking_task);
+      } else {
+        $('#invalid-email-modal').modal('show');
+      }
+    }
+  </script>
+{% endif %}
\ No newline at end of file
diff --git a/swh/web/templates/includes/vault-ui.html b/swh/web/templates/includes/vault-ui.html
new file mode 100644
index 00000000..c0dc1d03
--- /dev/null
+++ b/swh/web/templates/includes/vault-ui.html
@@ -0,0 +1,142 @@
+{% load static %}
+
+<div class="panel panel-default" style="overflow-x: auto;">
+  <div class="panel-heading">
+      <h2>Download content from the Software Heritage Vault</h2>
+  </div>
+  <div class="panel-body">
+    <p>
+      This interface enables to track the status of the different Software Heritage
+      Vault cooking tasks created during the current browsing session.
+    </p>
+    <p>
+      Once a cooking task is finished, a link will be made available in order to
+      download the associated archive.
+    </p>
+    <div class="table-responsive">
+      <table class="table swh-table" id="vault-cooking-tasks">
+        <thead>
+          <tr>
+            <th>Object type</th>
+            <th>Object id</th>
+            <th>Email notification</th>
+            <th>Cooking status</th>
+            <th style="width: 320px"></th>
+          </tr>
+        </thead>
+        <tbody></tbody>
+      </table>
+    </div>
+  </div>
+</div>
+
+<script>
+  var cook_dir_url = "{% url 'vault-cook-directory' 'ffff' %}";
+  var cook_rev_url = "{% url 'vault-cook-revision_gitfast' 'ffff' %}";
+  var browse_dir_url = "{% url 'browse-directory' 'ffff' %}";
+  var browse_rev_url = "{% url 'browse-revision' 'ffff' %}";
+
+  var progress = '<div class="progress" style="margin-bottom: 0px;"> \
+                    <div class="progress-bar progress-bar-success progress-bar-striped" \
+                         role="progressbar" aria-valuenow="100" aria-valuemin="0" \
+                         aria-valuemax="100" style="width: 100%;height: 100%;"> \
+                    </div> \
+                  </div>';
+
+  function check_vault_cooking_tasks() {
+    var vault_cooking_tasks = JSON.parse(sessionStorage.getItem("swh-vault-cooking-tasks"));
+    if (!vault_cooking_tasks) {
+      return;
+    }
+    var cooking_urls = [];
+    var tasks = {};
+    for (var i = 0 ; i < vault_cooking_tasks.length ; ++i) {
+      var cooking_task = vault_cooking_tasks[i];
+      tasks[cooking_task.object_id] = cooking_task;
+      var cooking_url;
+      if (cooking_task.object_type == 'directory') {
+        cooking_url = cook_dir_url.replace('ffff', cooking_task.object_id);
+      } else {
+        cooking_url = cook_rev_url.replace('ffff', cooking_task.object_id);
+      }
+      if (cooking_task.status != 'done' && cooking_task.status != 'failed') {
+        cooking_urls.push($.ajax(cooking_url));
+      }
+    }
+    $.when.apply($, cooking_urls).then(function() {
+      $("#vault-cooking-tasks tbody tr").remove();
+      var table = $("#vault-cooking-tasks tbody");
+      for (var i = 0 ; i < cooking_urls.length ; ++i) {
+        var resp;
+        if (cooking_urls.length == 1) {
+          resp = arguments[i];
+        } else {
+          resp = arguments[i][0];
+        }
+        var cooking_task = tasks[resp.obj_id];
+        cooking_task.status = resp.status;
+        cooking_task.fetch_url = resp.fetch_url;
+      }
+      for (var i = 0 ; i < vault_cooking_tasks.length ; ++i) {
+        var cooking_task = vault_cooking_tasks[i];
+        var browse_url;
+        if (cooking_task.object_type == 'directory') {
+          browse_url = browse_dir_url.replace('ffff', cooking_task.object_id);
+        } else {
+          browse_url = browse_rev_url.replace('ffff', cooking_task.object_id);
+        }
+
+        var progress_bar = $.parseHTML(progress)[0];
+        var progress_bar_content = $(progress_bar).find('.progress-bar');
+        if (cooking_task.status == 'failed') {
+          progress_bar_content.css('background-image', 'none');
+          progress_bar_content.css('background-color', 'red');
+        }
+        progress_bar_content.text(cooking_task.status);
+        if (cooking_task.status == 'pending') {
+          progress_bar_content.addClass('active');
+        } else if (cooking_task.status == 'done') {
+          progress_bar_content.removeClass('progress-bar-striped');
+        }
+        var table_row;
+        if (cooking_task.object_type == 'directory') {
+          table_row = '<tr title="Once downloaded, the directory can be extracted with the ' +
+                      'following command:\n\n$ tar xvzf ' + cooking_task.object_id + '.tar.gz">';
+        } else {
+          table_row = '<tr title="Once downloaded, the git repository can be imported with the ' +
+                      'following commands:\n\n$ git init\n$ zcat ' + cooking_task.object_id + '.gitfast.gz | git fast-import">';
+        }
+        if (cooking_task.object_type == 'directory') {
+          table_row += '<td><i class="fa fa-folder fa-fw" aria-hidden="true"></i>directory</td>';
+        } else {
+          table_row += '<td><i class="octicon octicon-git-commit fa-fw"></i>revision</td>';
+        }
+        table_row += '<td><a href="' + browse_url + '">' + cooking_task.object_id + '</a></td>';
+        table_row += '<td>' + (cooking_task.email || 'none') + '</td>';
+        table_row += '<td>' + progress_bar.outerHTML + '</td>';
+        var dl_link = 'Waiting for download link to be available';
+        if (cooking_task.status == 'done') {
+          dl_link = '<a class="btn btn-md btn-swh" href="' + cooking_task.fetch_url +
+                    '"><i class="fa fa-download fa-fw" aria-hidden="true"></i>Download</a>';
+        }
+        table_row += '<td  style="width: 320px">' + dl_link + '</td>';
+        table_row += '</tr>';
+        table.append(table_row);
+      }
+      sessionStorage.setItem("swh-vault-cooking-tasks", JSON.stringify(vault_cooking_tasks));
+      check_vault_id = setTimeout(check_vault_cooking_tasks, polling_interval);
+    });
+  }
+
+  var polling_interval = 5000;
+
+  var check_vault_id = setTimeout(check_vault_cooking_tasks, polling_interval);
+
+  $(document).on('shown.bs.tab', 'a[data-toggle="tab"]', function (e) {
+    if (e.target.text == 'Vault') {
+      clearTimeout(check_vault_id);
+      check_vault_cooking_tasks();
+    }
+  });
+
+</script>
\ No newline at end of file
diff --git a/swh/web/tests/browse/views/test_directory.py b/swh/web/tests/browse/views/test_directory.py
index 42fe069d..d9181b58 100644
--- a/swh/web/tests/browse/views/test_directory.py
+++ b/swh/web/tests/browse/views/test_directory.py
@@ -1,128 +1,130 @@
 # Copyright (C) 2017-2018  The Software Heritage developers
 # See the AUTHORS file at the top-level directory of this distribution
 # License: GNU General Public License version 3, or any later version
 # See top-level LICENSE file for more information
 
 from unittest.mock import patch
 from nose.tools import istest, nottest
 
 from django.test import TestCase
 
 from swh.web.common.exc import BadInputExc, NotFoundExc
 from swh.web.common.utils import reverse
 from swh.web.common.utils import gen_path_info
 from swh.web.tests.testbase import SWHWebTestBase
 
 from .data.directory_test_data import (
     stub_root_directory_sha1, stub_root_directory_data,
     stub_sub_directory_path, stub_sub_directory_data
 )
 
 
 class SwhBrowseDirectoryTest(SWHWebTestBase, TestCase):
 
     @nottest
     def directory_view(self, root_directory_sha1, directory_entries,
                        path=None):
         dirs = [e for e in directory_entries if e['type'] == 'dir']
         files = [e for e in directory_entries if e['type'] == 'file']
 
         url_args = {'sha1_git': root_directory_sha1}
         if path:
             url_args['path'] = path
 
         url = reverse('browse-directory',
                       kwargs=url_args)
 
         root_dir_url = reverse('browse-directory',
                                kwargs={'sha1_git': root_directory_sha1})
 
         resp = self.client.get(url)
 
         self.assertEquals(resp.status_code, 200)
         self.assertTemplateUsed('directory.html')
         self.assertContains(resp, '<a href="' + root_dir_url + '">' +
                             root_directory_sha1[:7] + '</a>')
         self.assertContains(resp, '<td class="swh-directory">',
                             count=len(dirs))
         self.assertContains(resp, '<td class="swh-content">',
                             count=len(files))
 
         for d in dirs:
             dir_path = d['name']
             if path:
                 dir_path = "%s/%s" % (path, d['name'])
             dir_url = reverse('browse-directory',
                               kwargs={'sha1_git': root_directory_sha1,
                                       'path': dir_path})
             self.assertContains(resp, dir_url)
 
         for f in files:
             file_path = "%s/%s" % (root_directory_sha1, f['name'])
             if path:
                 file_path = "%s/%s/%s" % (root_directory_sha1, path, f['name'])
             query_string = 'sha1_git:' + f['target']
             file_url = reverse('browse-content',
                                kwargs={'query_string': query_string},
                                query_params={'path': file_path})
             self.assertContains(resp, file_url)
 
         path_info = gen_path_info(path)
 
         self.assertContains(resp, '<li class="swh-path">',
                             count=len(path_info)+1)
         self.assertContains(resp, '<a href="%s">%s</a>' %
                                   (root_dir_url, root_directory_sha1[:7]))
 
         for p in path_info:
             dir_url = reverse('browse-directory',
                               kwargs={'sha1_git': root_directory_sha1,
                                       'path': p['path']})
             self.assertContains(resp, '<a href="%s">%s</a>' %
                                       (dir_url, p['name']))
 
+        self.assertContains(resp, '<button id="vault-cook-directory"')
+
     @patch('swh.web.browse.utils.service')
     @istest
     def root_directory_view(self, mock_service):
         mock_service.lookup_directory.return_value = \
             stub_root_directory_data
 
         self.directory_view(stub_root_directory_sha1, stub_root_directory_data)
 
     @patch('swh.web.browse.utils.service')
     @patch('swh.web.browse.views.directory.service')
     @istest
     def sub_directory_view(self, mock_directory_service, mock_utils_service):
         mock_utils_service.lookup_directory.return_value = \
             stub_sub_directory_data
         mock_directory_service.lookup_directory_with_path.return_value = \
             {'target': '120c39eeb566c66a77ce0e904d29dfde42228adc'}
 
         self.directory_view(stub_root_directory_sha1, stub_sub_directory_data,
                             stub_sub_directory_path)
 
     @patch('swh.web.browse.utils.service')
     @patch('swh.web.browse.views.directory.service')
     @istest
     def directory_request_errors(self, mock_directory_service,
                                  mock_utils_service):
 
         mock_utils_service.lookup_directory.side_effect = \
             BadInputExc('directory not found')
 
         dir_url = reverse('browse-directory',
                           kwargs={'sha1_git': '1253456'})
 
         resp = self.client.get(dir_url)
         self.assertEquals(resp.status_code, 400)
         self.assertTemplateUsed('error.html')
 
         mock_utils_service.lookup_directory.side_effect = \
             NotFoundExc('directory not found')
 
         dir_url = reverse('browse-directory',
                           kwargs={'sha1_git': '1253456'})
 
         resp = self.client.get(dir_url)
         self.assertEquals(resp.status_code, 404)
         self.assertTemplateUsed('error.html')
diff --git a/swh/web/tests/browse/views/test_origin.py b/swh/web/tests/browse/views/test_origin.py
index fc68019a..09678da8 100644
--- a/swh/web/tests/browse/views/test_origin.py
+++ b/swh/web/tests/browse/views/test_origin.py
@@ -1,718 +1,720 @@
 # Copyright (C) 2017-2018  The Software Heritage developers
 # See the AUTHORS file at the top-level directory of this distribution
 # License: GNU General Public License version 3, or any later version
 # See top-level LICENSE file for more information
 
 # flake8: noqa
 
 from unittest.mock import patch
 from nose.tools import istest, nottest
 
 from django.test import TestCase
 from django.utils.html import escape
 
 from swh.web.common.exc import NotFoundExc
 from swh.web.common.utils import (
     reverse, gen_path_info, format_utc_iso_date,
     parse_timestamp
 )
 from swh.web.tests.testbase import SWHWebTestBase
 
 from .data.origin_test_data import (
     origin_info_test_data,
     origin_visits_test_data,
     stub_content_origin_info, stub_content_origin_visit_id,
     stub_content_origin_visit_unix_ts, stub_content_origin_visit_iso_date,
     stub_content_origin_branch,
     stub_content_origin_visits, stub_content_origin_occurrences,
     stub_origin_info, stub_visit_id,
     stub_origin_visits, stub_origin_occurrences,
     stub_origin_root_directory_entries, stub_origin_master_branch,
     stub_origin_root_directory_sha1, stub_origin_sub_directory_path,
     stub_origin_sub_directory_entries, stub_visit_unix_ts, stub_visit_iso_date
 )
 
 from .data.content_test_data import (
     stub_content_root_dir,
     stub_content_text_data,
     stub_content_text_path
 )
 
 
 class SwhBrowseOriginTest(SWHWebTestBase, TestCase):
 
     @patch('swh.web.browse.views.origin.get_origin_visits')
     @patch('swh.web.browse.views.origin.service')
     @istest
     def origin_browse(self, mock_service, mock_get_origin_visits):
         mock_service.lookup_origin.return_value = origin_info_test_data
         mock_get_origin_visits.return_value = origin_visits_test_data
 
         url = reverse('browse-origin',
                       kwargs={'origin_type': origin_info_test_data['type'],
                               'origin_url': origin_info_test_data['url']})
         resp = self.client.get(url)
 
         self.assertEquals(resp.status_code, 200)
         self.assertTemplateUsed('origin.html')
         self.assertContains(resp, '<pre>%s</pre>' % origin_info_test_data['type'])
         self.assertContains(resp, '<pre><a href="%s">%s</a></pre>' %
                                   (origin_info_test_data['url'],
                                    origin_info_test_data['url']))
 
         self.assertContains(resp, '<td class="swh-origin-visit">',
                             count=len(origin_visits_test_data))
 
         for visit in origin_visits_test_data:
             visit_date_iso = format_utc_iso_date(visit['date'], '%Y-%m-%dT%H:%M:%S')
             visit_date = format_utc_iso_date(visit['date'])
             browse_url = reverse('browse-origin-directory',
                                  kwargs={'origin_type': origin_info_test_data['type'],
                                          'origin_url': origin_info_test_data['url'],
                                          'timestamp': visit_date_iso})
             self.assertContains(resp, 'href="%s">%s</a>' %
                                 (browse_url, visit_date))
 
     @nottest
     def origin_content_view_test(self, origin_info, origin_visits,
                                  origin_branches, origin_releases,
                                  origin_branch,
                                  root_dir_sha1, content_sha1,
                                  content_path, content_data,
                                  content_language,
                                  visit_id=None, timestamp=None):
 
         url_args = {'origin_type': origin_info['type'],
                     'origin_url': origin_info['url'],
                     'path': content_path}
 
         if not visit_id:
             visit_id = origin_visits[-1]['visit']
 
         query_params = {}
 
         if timestamp:
             url_args['timestamp'] = timestamp
 
         if visit_id:
             query_params['visit_id'] = visit_id
 
         url = reverse('browse-origin-content',
                       kwargs=url_args,
                       query_params=query_params)
 
         resp = self.client.get(url)
         self.assertEquals(resp.status_code, 200)
         self.assertTemplateUsed('content.html')
 
         self.assertContains(resp, '<code class="%s">' % content_language)
         self.assertContains(resp, escape(content_data))
 
         split_path = content_path.split('/')
 
         filename = split_path[-1]
         path = content_path.replace(filename, '')[:-1]
 
         path_info = gen_path_info(path)
 
         del url_args['path']
 
         if timestamp:
             url_args['timestamp'] = \
                 format_utc_iso_date(parse_timestamp(timestamp).isoformat(),
                                     '%Y-%m-%dT%H:%M:%S')
 
         root_dir_url = reverse('browse-origin-directory',
                                kwargs=url_args,
                                query_params=query_params)
 
         self.assertContains(resp, '<li class="swh-path">',
                             count=len(path_info)+1)
 
 
         self.assertContains(resp, '<a href="%s">%s</a>' %
                             (root_dir_url, root_dir_sha1[:7]))
 
         for p in path_info:
             url_args['path'] = p['path']
             dir_url = reverse('browse-origin-directory',
                               kwargs=url_args,
                               query_params=query_params)
             self.assertContains(resp, '<a href="%s">%s</a>' %
                                 (dir_url, p['name']))
 
         self.assertContains(resp, '<li>%s</li>' % filename)
 
         query_string = 'sha1_git:' + content_sha1
 
         url_raw = reverse('browse-content-raw',
                           kwargs={'query_string': query_string},
                           query_params={'filename': filename})
         self.assertContains(resp, url_raw)
 
         del url_args['path']
 
         origin_branches_url = \
                 reverse('browse-origin-branches',
                         kwargs=url_args,
                         query_params=query_params)
 
         self.assertContains(resp, '<a href="%s">Branches (%s)</a>' %
             (origin_branches_url, len(origin_branches)))
 
         origin_releases_url = \
                 reverse('browse-origin-releases',
                         kwargs=url_args,
                         query_params=query_params)
 
         self.assertContains(resp, '<a href="%s">Releases (%s)</a>' %
             (origin_releases_url, len(origin_releases)))
 
         self.assertContains(resp, '<li class="swh-branch">',
                             count=len(origin_branches))
 
         url_args['path'] = content_path
 
         for branch in origin_branches:
             query_params['branch'] = branch['name']
             root_dir_branch_url = \
                 reverse('browse-origin-content',
                         kwargs=url_args,
                         query_params=query_params)
 
         self.assertContains(resp, '<a href="%s">' % root_dir_branch_url)
 
         self.assertContains(resp, '<li class="swh-release">',
                             count=len(origin_releases))
 
         query_params['branch'] = None
         for release in origin_releases:
             query_params['release'] = release['name']
             root_dir_release_url = \
                 reverse('browse-origin-content',
                         kwargs=url_args,
                         query_params=query_params)
 
             self.assertContains(resp, '<a href="%s">' % root_dir_release_url)
 
     @patch('swh.web.browse.utils.get_origin_visits')
     @patch('swh.web.browse.utils.get_origin_visit_occurrences')
     @patch('swh.web.browse.views.origin.service')
     @patch('swh.web.browse.utils.service')
     @patch('swh.web.browse.views.origin.request_content')
     @istest
     def origin_content_view(self, mock_request_content, mock_utils_service,
                             mock_service, mock_get_origin_visit_occurrences,
                             mock_get_origin_visits):
 
         stub_content_text_sha1 = stub_content_text_data['checksums']['sha1']
         mock_get_origin_visits.return_value = stub_content_origin_visits
         mock_get_origin_visit_occurrences.return_value = stub_content_origin_occurrences
         mock_service.lookup_directory_with_path.return_value = \
             {'target': stub_content_text_sha1}
         mock_request_content.return_value = stub_content_text_data
         mock_utils_service.lookup_origin.return_value = stub_content_origin_info
 
         self.origin_content_view_test(stub_content_origin_info,
                                       stub_content_origin_visits,
                                       stub_content_origin_occurrences[0],
                                       stub_content_origin_occurrences[1],
                                       stub_content_origin_branch,
                                       stub_content_root_dir,
                                       stub_content_text_sha1,
                                       stub_content_text_path,
                                       stub_content_text_data['raw_data'],
                                       'cpp')
 
         self.origin_content_view_test(stub_content_origin_info,
                                       stub_content_origin_visits,
                                       stub_content_origin_occurrences[0],
                                       stub_content_origin_occurrences[1],
                                       stub_content_origin_branch,
                                       stub_content_root_dir,
                                       stub_content_text_sha1,
                                       stub_content_text_path,
                                       stub_content_text_data['raw_data'],
                                       'cpp',
                                       visit_id=stub_content_origin_visit_id)
 
         self.origin_content_view_test(stub_content_origin_info,
                                       stub_content_origin_visits,
                                       stub_content_origin_occurrences[0],
                                       stub_content_origin_occurrences[1],
                                       stub_content_origin_branch,
                                       stub_content_root_dir,
                                       stub_content_text_sha1,
                                       stub_content_text_path,
                                       stub_content_text_data['raw_data'],
                                       'cpp',
                                       timestamp=stub_content_origin_visit_unix_ts)
 
         self.origin_content_view_test(stub_content_origin_info,
                                       stub_content_origin_visits,
                                       stub_content_origin_occurrences[0],
                                       stub_content_origin_occurrences[1],
                                       stub_content_origin_branch,
                                       stub_content_root_dir,
                                       stub_content_text_sha1,
                                       stub_content_text_path,
                                       stub_content_text_data['raw_data'],
                                       'cpp',
                                       timestamp=stub_content_origin_visit_iso_date)
 
     @nottest
     def origin_directory_view(self, origin_info, origin_visits,
                               origin_branches, origin_releases, origin_branch,
                               root_directory_sha1, directory_entries,
                               visit_id=None, timestamp=None, path=None):
 
         dirs = [e for e in directory_entries
                 if e['type'] == 'dir']
         files = [e for e in directory_entries
                  if e['type'] == 'file']
 
         if not visit_id:
             visit_id = origin_visits[-1]['visit']
 
         url_args = {'origin_type': origin_info['type'],
                     'origin_url': origin_info['url']}
 
         query_params = {}
 
         if timestamp:
             url_args['timestamp'] = timestamp
         else:
             query_params['visit_id'] = visit_id
 
         if path:
             url_args['path'] = path
 
         url = reverse('browse-origin-directory',
                       kwargs=url_args,
                       query_params=query_params)
 
         resp = self.client.get(url)
 
         self.assertEquals(resp.status_code, 200)
         self.assertTemplateUsed('directory.html')
         self.assertContains(resp, '<td class="swh-directory">',
                             count=len(dirs))
         self.assertContains(resp, '<td class="swh-content">',
                             count=len(files))
 
         if timestamp:
             url_args['timestamp'] = \
                 format_utc_iso_date(parse_timestamp(timestamp).isoformat(),
                                     '%Y-%m-%dT%H:%M:%S')
 
         for d in dirs:
             dir_path = d['name']
             if path:
                 dir_path = "%s/%s" % (path, d['name'])
             dir_url_args = dict(url_args)
             dir_url_args['path'] = dir_path
             dir_url = reverse('browse-origin-directory',
                               kwargs=dir_url_args,
                               query_params=query_params)
             self.assertContains(resp, dir_url)
 
         for f in files:
             file_path = f['name']
             if path:
                 file_path = "%s/%s" % (path, f['name'])
             file_url_args = dict(url_args)
             file_url_args['path'] = file_path
             file_url = reverse('browse-origin-content',
                                kwargs=file_url_args,
                                query_params=query_params)
             self.assertContains(resp, file_url)
 
         if 'path' in url_args:
             del url_args['path']
 
         root_dir_branch_url = \
             reverse('browse-origin-directory',
                     kwargs=url_args,
                     query_params=query_params)
 
         nb_bc_paths = 1
         if path:
             nb_bc_paths = len(path.split('/')) + 1
 
         self.assertContains(resp, '<li class="swh-path">', count=nb_bc_paths)
         self.assertContains(resp, '<a href="%s">%s</a>' %
                                   (root_dir_branch_url,
                                    root_directory_sha1[:7]))
 
         origin_branches_url = \
                 reverse('browse-origin-branches',
                         kwargs=url_args,
                         query_params=query_params)
 
         self.assertContains(resp, '<a href="%s">Branches (%s)</a>' %
             (origin_branches_url, len(origin_branches)))
 
         origin_releases_url = \
                 reverse('browse-origin-releases',
                         kwargs=url_args,
                         query_params=query_params)
 
         self.assertContains(resp, '<a href="%s">Releases (%s)</a>' %
             (origin_releases_url, len(origin_releases)))
 
         if path:
             url_args['path'] = path
 
         self.assertContains(resp, '<li class="swh-branch">',
                             count=len(origin_branches))
 
         for branch in origin_branches:
             query_params['branch'] = branch['name']
             root_dir_branch_url = \
                 reverse('browse-origin-directory',
                         kwargs=url_args,
                         query_params=query_params)
 
             self.assertContains(resp, '<a href="%s">' % root_dir_branch_url)
 
         self.assertContains(resp, '<li class="swh-release">',
                             count=len(origin_releases))
 
         query_params['branch'] = None
         for release in origin_releases:
             query_params['release'] = release['name']
             root_dir_release_url = \
                 reverse('browse-origin-directory',
                         kwargs=url_args,
                         query_params=query_params)
 
             self.assertContains(resp, '<a href="%s">' % root_dir_release_url)
+        self.assertContains(resp, '<button id="vault-cook-directory"')
+        self.assertContains(resp, '<button id="vault-cook-revision"')
 
     @patch('swh.web.browse.utils.get_origin_visits')
     @patch('swh.web.browse.utils.get_origin_visit_occurrences')
     @patch('swh.web.browse.utils.service')
     @patch('swh.web.browse.views.origin.service')
     @istest
     def origin_root_directory_view(self, mock_origin_service,
                                    mock_utils_service,
                                    mock_get_origin_visit_occurrences,
                                    mock_get_origin_visits):
 
         mock_get_origin_visits.return_value = stub_origin_visits
         mock_get_origin_visit_occurrences.return_value = stub_origin_occurrences
         mock_utils_service.lookup_directory.return_value = \
             stub_origin_root_directory_entries
         mock_utils_service.lookup_origin.return_value = stub_origin_info
 
         self.origin_directory_view(stub_origin_info, stub_origin_visits,
                                    stub_origin_occurrences[0],
                                    stub_origin_occurrences[1],
                                    stub_origin_master_branch,
                                    stub_origin_root_directory_sha1,
                                    stub_origin_root_directory_entries)
 
         self.origin_directory_view(stub_origin_info, stub_origin_visits,
                                    stub_origin_occurrences[0],
                                    stub_origin_occurrences[1],
                                    stub_origin_master_branch,
                                    stub_origin_root_directory_sha1,
                                    stub_origin_root_directory_entries,
                                    visit_id=stub_visit_id)
 
         self.origin_directory_view(stub_origin_info, stub_origin_visits,
                                    stub_origin_occurrences[0],
                                    stub_origin_occurrences[1],
                                    stub_origin_master_branch,
                                    stub_origin_root_directory_sha1,
                                    stub_origin_root_directory_entries,
                                    timestamp=stub_visit_unix_ts)
 
         self.origin_directory_view(stub_origin_info, stub_origin_visits,
                                    stub_origin_occurrences[0],
                                    stub_origin_occurrences[1],
                                    stub_origin_master_branch,
                                    stub_origin_root_directory_sha1,
                                    stub_origin_root_directory_entries,
                                    timestamp=stub_visit_iso_date)
 
     @patch('swh.web.browse.utils.get_origin_visits')
     @patch('swh.web.browse.utils.get_origin_visit_occurrences')
     @patch('swh.web.browse.utils.service')
     @patch('swh.web.browse.views.origin.service')
     @istest
     def origin_sub_directory_view(self, mock_origin_service,
                                   mock_utils_service,
                                   mock_get_origin_visit_occurrences,
                                   mock_get_origin_visits):
 
         mock_get_origin_visits.return_value = stub_origin_visits
         mock_get_origin_visit_occurrences.return_value = stub_origin_occurrences
         mock_utils_service.lookup_directory.return_value = \
             stub_origin_sub_directory_entries
         mock_origin_service.lookup_directory_with_path.return_value = \
             {'target': '120c39eeb566c66a77ce0e904d29dfde42228adb'}
         mock_utils_service.lookup_origin.return_value = stub_origin_info
 
         self.origin_directory_view(stub_origin_info, stub_origin_visits,
                                    stub_origin_occurrences[0],
                                    stub_origin_occurrences[1],
                                    stub_origin_master_branch,
                                    stub_origin_root_directory_sha1,
                                    stub_origin_sub_directory_entries,
                                    path=stub_origin_sub_directory_path)
 
         self.origin_directory_view(stub_origin_info, stub_origin_visits,
                                    stub_origin_occurrences[0],
                                    stub_origin_occurrences[1],
                                    stub_origin_master_branch,
                                    stub_origin_root_directory_sha1,
                                    stub_origin_sub_directory_entries,
                                    visit_id=stub_visit_id,
                                    path=stub_origin_sub_directory_path)
 
         self.origin_directory_view(stub_origin_info, stub_origin_visits,
                                    stub_origin_occurrences[0],
                                    stub_origin_occurrences[1],
                                    stub_origin_master_branch,
                                    stub_origin_root_directory_sha1,
                                    stub_origin_sub_directory_entries,
                                    timestamp=stub_visit_unix_ts,
                                    path=stub_origin_sub_directory_path)
 
         self.origin_directory_view(stub_origin_info, stub_origin_visits,
                                    stub_origin_occurrences[0],
                                    stub_origin_occurrences[1],
                                    stub_origin_master_branch,
                                    stub_origin_root_directory_sha1,
                                    stub_origin_sub_directory_entries,
                                    timestamp=stub_visit_iso_date,
                                    path=stub_origin_sub_directory_path)
 
     @patch('swh.web.browse.views.origin.request_content')
     @patch('swh.web.browse.utils.get_origin_visits')
     @patch('swh.web.browse.utils.get_origin_visit_occurrences')
     @patch('swh.web.browse.utils.service')
     @patch('swh.web.browse.views.origin.service')
     @istest
     def origin_request_errors(self, mock_origin_service,
                               mock_utils_service,
                               mock_get_origin_visit_occurrences,
                               mock_get_origin_visits,
                               mock_request_content):
 
         mock_origin_service.lookup_origin.side_effect = \
             NotFoundExc('origin not found')
         url = reverse('browse-origin',
                       kwargs={'origin_type': 'foo',
                               'origin_url': 'bar'})
         resp = self.client.get(url)
         self.assertEquals(resp.status_code, 404)
         self.assertTemplateUsed('error.html')
         self.assertContains(resp, "origin not found", status_code=404)
 
         mock_utils_service.lookup_origin.side_effect = None
         mock_utils_service.lookup_origin.return_value = origin_info_test_data
         mock_get_origin_visits.return_value = []
         url = reverse('browse-origin-directory',
                       kwargs={'origin_type': 'foo',
                               'origin_url': 'bar'})
         resp = self.client.get(url)
         self.assertEquals(resp.status_code, 404)
         self.assertTemplateUsed('error.html')
         self.assertContains(resp, "No SWH visit", status_code=404)
 
         mock_get_origin_visits.return_value = stub_origin_visits
         mock_get_origin_visit_occurrences.side_effect = \
             NotFoundExc('visit not found')
         url = reverse('browse-origin-directory',
                       kwargs={'origin_type': 'foo',
                               'origin_url': 'bar'},
                       query_params={'visit_id': len(stub_origin_visits)+1})
         resp = self.client.get(url)
         self.assertEquals(resp.status_code, 404)
         self.assertTemplateUsed('error.html')
         self.assertRegex(resp.content.decode('utf-8'), 'Visit.*not found')
 
         mock_get_origin_visits.return_value = stub_origin_visits
         mock_get_origin_visit_occurrences.side_effect = None
         mock_get_origin_visit_occurrences.return_value = ([], [])
         url = reverse('browse-origin-directory',
                       kwargs={'origin_type': 'foo',
                               'origin_url': 'bar'})
         resp = self.client.get(url)
         self.assertEquals(resp.status_code, 404)
         self.assertTemplateUsed('error.html')
         self.assertRegex(resp.content.decode('utf-8'),
                          'Origin.*has an empty list of branches')
 
         mock_get_origin_visit_occurrences.return_value = stub_origin_occurrences
         mock_utils_service.lookup_directory.side_effect = \
             NotFoundExc('Directory not found')
         url = reverse('browse-origin-directory',
                       kwargs={'origin_type': 'foo',
                               'origin_url': 'bar'})
         resp = self.client.get(url)
         self.assertEquals(resp.status_code, 404)
         self.assertTemplateUsed('error.html')
         self.assertContains(resp, 'Directory not found', status_code=404)
 
         mock_origin_service.lookup_origin.side_effect = None
         mock_origin_service.lookup_origin.return_value = origin_info_test_data
         mock_get_origin_visits.return_value = []
         url = reverse('browse-origin-content',
                       kwargs={'origin_type': 'foo',
                               'origin_url': 'bar',
                               'path': 'foo'})
         resp = self.client.get(url)
         self.assertEquals(resp.status_code, 404)
         self.assertTemplateUsed('error.html')
         self.assertContains(resp, "No SWH visit", status_code=404)
 
         mock_get_origin_visits.return_value = stub_origin_visits
         mock_get_origin_visit_occurrences.side_effect = \
             NotFoundExc('visit not found')
         url = reverse('browse-origin-content',
                       kwargs={'origin_type': 'foo',
                               'origin_url': 'bar',
                               'path': 'foo'},
                       query_params={'visit_id': len(stub_origin_visits)+1})
         resp = self.client.get(url)
         self.assertEquals(resp.status_code, 404)
         self.assertTemplateUsed('error.html')
         self.assertRegex(resp.content.decode('utf-8'), 'Visit.*not found')
 
         mock_get_origin_visits.return_value = stub_origin_visits
         mock_get_origin_visit_occurrences.side_effect = None
         mock_get_origin_visit_occurrences.return_value = ([], [])
         url = reverse('browse-origin-content',
                       kwargs={'origin_type': 'foo',
                               'origin_url': 'bar',
                               'path': 'baz'})
         resp = self.client.get(url)
         self.assertEquals(resp.status_code, 404)
         self.assertTemplateUsed('error.html')
         self.assertRegex(resp.content.decode('utf-8'),
                          'Origin.*has an empty list of branches')
 
         mock_get_origin_visit_occurrences.return_value = stub_origin_occurrences
         mock_origin_service.lookup_directory_with_path.return_value = \
             {'target': stub_content_text_data['checksums']['sha1']}
         mock_request_content.side_effect = \
             NotFoundExc('Content not found')
         url = reverse('browse-origin-content',
                       kwargs={'origin_type': 'foo',
                               'origin_url': 'bar',
                               'path': 'baz'})
         resp = self.client.get(url)
         self.assertEquals(resp.status_code, 404)
         self.assertTemplateUsed('error.html')
         self.assertContains(resp, 'Content not found', status_code=404)
 
 
     @patch('swh.web.browse.utils.get_origin_visits')
     @patch('swh.web.browse.utils.get_origin_visit_occurrences')
     @patch('swh.web.browse.utils.service')
     @patch('swh.web.browse.views.origin.service')
     @istest
     def origin_branches(self, mock_origin_service,
                         mock_utils_service,
                         mock_get_origin_visit_occurrences,
                         mock_get_origin_visits):
         mock_get_origin_visits.return_value = stub_origin_visits
         mock_get_origin_visit_occurrences.return_value = stub_origin_occurrences
         mock_utils_service.lookup_origin.return_value = stub_origin_info
 
         url_args = {'origin_type': stub_origin_info['type'],
                     'origin_url': stub_origin_info['url']}
 
         url = reverse('browse-origin-branches',
                       kwargs=url_args)
 
         resp = self.client.get(url)
         self.assertEquals(resp.status_code, 200)
         self.assertTemplateUsed('branches.html')
 
         origin_branches = stub_origin_occurrences[0]
         origin_releases = stub_origin_occurrences[1]
 
         origin_branches_url = \
                 reverse('browse-origin-branches',
                         kwargs=url_args)
 
         self.assertContains(resp, '<a href="%s">Branches (%s)</a>' %
             (origin_branches_url, len(origin_branches)))
 
         origin_releases_url = \
                 reverse('browse-origin-releases',
                         kwargs=url_args)
 
         self.assertContains(resp, '<a href="%s">Releases (%s)</a>' %
             (origin_releases_url, len(origin_releases)))
 
         self.assertContains(resp, '<tr class="swh-origin-branch">',
                             count=len(origin_branches))
 
         for branch in origin_branches:
             browse_branch_url = reverse('browse-origin-directory',
                                         kwargs={'origin_type': stub_origin_info['type'],
                                                 'origin_url': stub_origin_info['url']},
                                         query_params={'branch': branch['name']})
             self.assertContains(resp, '<a href="%s">%s</a>' % (escape(browse_branch_url), branch['name']))
 
             browse_revision_url = reverse('browse-revision',
                                           kwargs={'sha1_git': branch['revision']},
                                           query_params={'origin_type': stub_origin_info['type'],
                                                         'origin_url': stub_origin_info['url']})
             self.assertContains(resp, '<a href="%s">%s</a>' % (escape(browse_revision_url), branch['revision'][:7]))
 
 
     @patch('swh.web.browse.utils.get_origin_visits')
     @patch('swh.web.browse.utils.get_origin_visit_occurrences')
     @patch('swh.web.browse.utils.service')
     @patch('swh.web.browse.views.origin.service')
     @istest
     def origin_releases(self, mock_origin_service,
                         mock_utils_service,
                         mock_get_origin_visit_occurrences,
                         mock_get_origin_visits):
         mock_get_origin_visits.return_value = stub_origin_visits
         mock_get_origin_visit_occurrences.return_value = stub_origin_occurrences
         mock_utils_service.lookup_origin.return_value = stub_origin_info
 
         url_args = {'origin_type': stub_origin_info['type'],
                     'origin_url': stub_origin_info['url']}
 
         url = reverse('browse-origin-releases',
                       kwargs=url_args)
 
         resp = self.client.get(url)
         self.assertEquals(resp.status_code, 200)
         self.assertTemplateUsed('releases.html')
 
         origin_branches = stub_origin_occurrences[0]
         origin_releases = stub_origin_occurrences[1]
 
         origin_branches_url = \
                 reverse('browse-origin-branches',
                         kwargs=url_args)
 
         self.assertContains(resp, '<a href="%s">Branches (%s)</a>' %
             (origin_branches_url, len(origin_branches)))
 
         origin_releases_url = \
                 reverse('browse-origin-releases',
                         kwargs=url_args)
 
         self.assertContains(resp, '<a href="%s">Releases (%s)</a>' %
             (origin_releases_url, len(origin_releases)))
 
         self.assertContains(resp, '<tr class="swh-origin-release">',
                             count=len(origin_releases))
 
         for release in origin_releases:
             browse_release_url = reverse('browse-release',
                                          kwargs={'sha1_git': release['id']},
                                          query_params={'origin_type': stub_origin_info['type'],
                                                        'origin_url': stub_origin_info['url']})
             self.assertContains(resp, '<a href="%s">%s</a>' % (escape(browse_release_url), release['name']))
 
diff --git a/swh/web/tests/browse/views/test_revision.py b/swh/web/tests/browse/views/test_revision.py
index d6bb595a..024942f5 100644
--- a/swh/web/tests/browse/views/test_revision.py
+++ b/swh/web/tests/browse/views/test_revision.py
@@ -1,259 +1,262 @@
 # Copyright (C) 2017-2018  The Software Heritage developers
 # See the AUTHORS file at the top-level directory of this distribution
 # License: GNU General Public License version 3, or any later version
 # See top-level LICENSE file for more information
 
 # flake8: noqa
 
 from unittest.mock import patch
 from nose.tools import istest
 from django.test import TestCase
 from django.utils.html import escape
 
 from swh.web.common.exc import NotFoundExc
 from swh.web.common.utils import reverse, format_utc_iso_date
 from swh.web.tests.testbase import SWHWebTestBase
 
 from .data.revision_test_data import (
     revision_id_test, revision_metadata_test,
     revision_history_log_test
 )
 
 from .data.origin_test_data import stub_origin_visits
 
 
 class SwhBrowseRevisionTest(SWHWebTestBase, TestCase):
 
     @patch('swh.web.browse.views.revision.service')
     @patch('swh.web.browse.utils.service')
     @istest
     def revision_browse(self, mock_service_utils, mock_service):
         mock_service.lookup_revision.return_value = revision_metadata_test
 
         url = reverse('browse-revision',
                       kwargs={'sha1_git': revision_id_test})
 
         author_id = revision_metadata_test['author']['id']
         author_name = revision_metadata_test['author']['name']
         committer_id = revision_metadata_test['committer']['id']
         committer_name = revision_metadata_test['committer']['name']
         dir_id = revision_metadata_test['directory']
 
         author_url = reverse('browse-person',
                              kwargs={'person_id': author_id})
         committer_url = reverse('browse-person',
                                 kwargs={'person_id': committer_id})
 
         directory_url = reverse('browse-directory',
                                 kwargs={'sha1_git': dir_id})
 
         history_url = reverse('browse-revision-log',
                               kwargs={'sha1_git': revision_id_test})
 
         resp = self.client.get(url)
 
         self.assertEquals(resp.status_code, 200)
         self.assertTemplateUsed('revision.html')
         self.assertContains(resp, '<a href="%s">%s</a>' %
                                   (author_url, author_name))
         self.assertContains(resp, '<a href="%s">%s</a>' %
                                   (committer_url, committer_name))
         self.assertContains(resp, directory_url)
         self.assertContains(resp, history_url)
 
         for parent in revision_metadata_test['parents']:
             parent_url = reverse('browse-revision',
                                  kwargs={'sha1_git': parent})
             self.assertContains(resp, '<a href="%s">%s</a>' %
                                 (parent_url, parent))
 
         author_date = revision_metadata_test['date']
         committer_date = revision_metadata_test['committer_date']
 
         message_lines = revision_metadata_test['message'].split('\n')
 
         self.assertContains(resp, format_utc_iso_date(author_date))
         self.assertContains(resp, format_utc_iso_date(committer_date))
         self.assertContains(resp, '<h2>%s</h2>%s' % (message_lines[0],
                                                      '\n'.join(message_lines[1:])))
 
         origin_info = {
             'id': '7416001',
             'type': 'git',
             'url': 'https://github.com/webpack/webpack'
         }
 
         mock_service_utils.lookup_origin.return_value = origin_info
         mock_service_utils.lookup_origin_visits.return_value = stub_origin_visits
         mock_service_utils.MAX_LIMIT = 20
 
         origin_directory_url = reverse('browse-origin-directory',
                                        kwargs={'origin_type': origin_info['type'],
                                                'origin_url': origin_info['url']},
                                        query_params={'revision': revision_id_test})
 
         origin_revision_log_url = reverse('browse-origin-log',
                                           kwargs={'origin_type': origin_info['type'],
                                                   'origin_url': origin_info['url']},
                                           query_params={'revision': revision_id_test})
 
         url = reverse('browse-revision',
                       kwargs={'sha1_git': revision_id_test},
                       query_params={'origin_type': origin_info['type'],
                                     'origin_url': origin_info['url']})
 
         resp = self.client.get(url)
 
         self.assertContains(resp, origin_directory_url)
 
         self.assertContains(resp, origin_revision_log_url)
 
         for parent in revision_metadata_test['parents']:
             parent_url = reverse('browse-revision',
                                  kwargs={'sha1_git': parent},
                                  query_params={'origin_type': origin_info['type'],
                                                'origin_url': origin_info['url']})
             self.assertContains(resp, '<a href="%s">%s</a>' %
                                 (parent_url, parent))
 
+        self.assertContains(resp, '<button id="vault-cook-directory"')
+        self.assertContains(resp, '<button id="vault-cook-revision"')
+
     @patch('swh.web.browse.views.revision.service')
     @istest
     def revision_log_browse(self, mock_service):
         per_page = 10
         mock_service.lookup_revision_log.return_value = \
             revision_history_log_test[:per_page+1]
 
         url = reverse('browse-revision-log',
                       kwargs={'sha1_git': revision_id_test},
                       query_params={'per_page': per_page})
 
         resp = self.client.get(url)
 
         prev_rev = revision_history_log_test[per_page]['id']
         next_page_url = reverse('browse-revision-log',
                                 kwargs={'sha1_git': prev_rev},
                                 query_params={'revs_breadcrumb': revision_id_test,
                                               'per_page': per_page})
 
         self.assertEquals(resp.status_code, 200)
         self.assertTemplateUsed('revision-log.html')
         self.assertContains(resp, '<tr class="swh-revision-log-entry">',
                             count=per_page)
         self.assertContains(resp, '<li class="disabled"><a>Newer</a></li>')
         self.assertContains(resp, '<li><a href="%s">Older</a></li>' %
                             escape(next_page_url))
 
         for log in revision_history_log_test[:per_page]:
             author_url = reverse('browse-person',
                                  kwargs={'person_id': log['author']['id']})
             revision_url = reverse('browse-revision',
                                    kwargs={'sha1_git': log['id']})
             directory_url = reverse('browse-directory',
                                     kwargs={'sha1_git': log['directory']})
             self.assertContains(resp, '<a href="%s">%s</a>' %
                                 (author_url, log['author']['name']))
             self.assertContains(resp, '<a href="%s">%s</a>' %
                                 (revision_url, log['id'][:7]))
             self.assertContains(resp, '<a href="%s">%s</a>' %
                                 (directory_url, 'Tree'))
 
         mock_service.lookup_revision_log.return_value = \
             revision_history_log_test[per_page:2*per_page+1]
 
         resp = self.client.get(next_page_url)
 
         prev_prev_rev = revision_history_log_test[2*per_page]['id']
         prev_page_url = reverse('browse-revision-log',
                                 kwargs={'sha1_git': revision_id_test},
                                 query_params={'per_page': per_page})
         next_page_url = reverse('browse-revision-log',
                                 kwargs={'sha1_git': prev_prev_rev},
                                 query_params={'revs_breadcrumb': revision_id_test + '/' + prev_rev,
                                               'per_page': per_page})
 
         self.assertEquals(resp.status_code, 200)
         self.assertTemplateUsed('revision-log.html')
         self.assertContains(resp, '<tr class="swh-revision-log-entry">',
                             count=per_page)
         self.assertContains(resp, '<li><a href="%s">Newer</a></li>' %
                             escape(prev_page_url))
         self.assertContains(resp, '<li><a href="%s">Older</a></li>' %
                             escape(next_page_url))
 
         mock_service.lookup_revision_log.return_value = \
             revision_history_log_test[2*per_page:3*per_page+1]
 
         resp = self.client.get(next_page_url)
 
         prev_prev_prev_rev = revision_history_log_test[3*per_page]['id']
         prev_page_url = reverse('browse-revision-log',
                                 kwargs={'sha1_git': prev_rev},
                                 query_params={'revs_breadcrumb': revision_id_test,
                                               'per_page': per_page})
         next_page_url = reverse('browse-revision-log',
                                 kwargs={'sha1_git': prev_prev_prev_rev},
                                 query_params={'revs_breadcrumb': revision_id_test + '/' + prev_rev + '/' + prev_prev_rev,
                                               'per_page': per_page})
 
         self.assertEquals(resp.status_code, 200)
         self.assertTemplateUsed('revision-log.html')
         self.assertContains(resp, '<tr class="swh-revision-log-entry">',
                             count=per_page)
         self.assertContains(resp, '<li><a href="%s">Newer</a></li>' %
                             escape(prev_page_url))
         self.assertContains(resp, '<li><a href="%s">Older</a></li>' %
                             escape(next_page_url))
 
         mock_service.lookup_revision_log.return_value = \
             revision_history_log_test[3*per_page:3*per_page+per_page//2]
 
         resp = self.client.get(next_page_url)
 
         prev_page_url = reverse('browse-revision-log',
                                 kwargs={'sha1_git': prev_prev_rev},
                                 query_params={'revs_breadcrumb': revision_id_test + '/' + prev_rev,
                                               'per_page': per_page})
 
         self.assertEquals(resp.status_code, 200)
         self.assertTemplateUsed('revision-log.html')
         self.assertContains(resp, '<tr class="swh-revision-log-entry">',
                             count=per_page//2)
         self.assertContains(resp, '<li class="disabled"><a>Older</a></li>')
         self.assertContains(resp, '<li><a href="%s">Newer</a></li>' %
                             escape(prev_page_url))
 
     @patch('swh.web.browse.utils.service')
     @patch('swh.web.browse.views.revision.service')
     @istest
     def revision_request_errors(self, mock_service, mock_utils_service):
         mock_service.lookup_revision.side_effect = \
             NotFoundExc('Revision not found')
         url = reverse('browse-revision',
                       kwargs={'sha1_git': revision_id_test})
         resp = self.client.get(url)
         self.assertEquals(resp.status_code, 404)
         self.assertTemplateUsed('error.html')
         self.assertContains(resp, 'Revision not found', status_code=404)
 
         mock_service.lookup_revision_log.side_effect = \
             NotFoundExc('Revision not found')
         url = reverse('browse-revision-log',
                       kwargs={'sha1_git': revision_id_test})
         resp = self.client.get(url)
         self.assertEquals(resp.status_code, 404)
         self.assertTemplateUsed('error.html')
         self.assertContains(resp, 'Revision not found', status_code=404)
 
         url = reverse('browse-revision',
                       kwargs={'sha1_git': revision_id_test},
                       query_params={'origin_type': 'git',
                                     'origin_url': 'https://github.com/foo/bar'})
 
         mock_service.lookup_revision.side_effect = None
         mock_utils_service.lookup_origin.side_effect = \
             NotFoundExc('Origin not found')
 
         resp = self.client.get(url)
         self.assertEquals(resp.status_code, 404)
         self.assertTemplateUsed('error.html')
         self.assertContains(resp, 'Origin not found', status_code=404)